/* Receipts — file upload + AI-powered receipt parse via Claude */

const Receipts = ({ goTo, txState, setTxState }) => {
  const [tab, setTab] = React.useState("vault"); // vault | scan
  const [uploadedFile, setUploadedFile] = React.useState(null); // { name, type, size, previewUrl, mimeType }
  const [scanning, setScanning] = React.useState(false);
  const [scanStatus, setScanStatus] = React.useState("");
  const [parsed, setParsed] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [viewing, setViewing] = React.useState(null); // tx being previewed in modal
  const [pasteText, setPasteText] = React.useState("");
  const [showPaste, setShowPaste] = React.useState(false);
  const [pendingReceiptId, setPendingReceiptId] = React.useState(null); // receipt DB ID from upload
  const [potentialMatches, setPotentialMatches] = React.useState(null); // null=not run, []=none
  const [linking, setLinking] = React.useState(null); // txId being linked

  // Vault filters
  const [vaultSearch, setVaultSearch] = React.useState("");
  const [vaultMonth, setVaultMonth] = React.useState("all");
  const [vaultType, setVaultType] = React.useState("all");
  const [vaultCat, setVaultCat] = React.useState("all");

  // Load receipt transactions independently — don't rely on txState date window
  const [receiptTxs, setReceiptTxs] = React.useState(null); // null = loading
  React.useEffect(() => {
    if (!window.CB_API) return;
    window.CB_API.getTransactions({ has_receipt: true, limit: 500 })
      .then(res => {
        const data = res.data ?? [];
        setReceiptTxs(data);
        // If navigated here from a transaction modal, open that receipt immediately
        if (window.__openReceiptTxId) {
          const target = data.find(t => t.id === window.__openReceiptTxId);
          if (target) { setViewing(target); setTab("vault"); }
          delete window.__openReceiptTxId;
        }
      })
      .catch(() => setReceiptTxs([]));
  }, []);

  // Merge: API receipts + any just-added ones not yet in DB response
  const localNew = txState.filter(t => t.receipt && !(receiptTxs ?? []).find(r => r.id === t.id));
  const receipts = receiptTxs === null
    ? txState.filter(t => t.receipt)           // still loading — use txState as fallback
    : [...localNew, ...(receiptTxs ?? [])];

  // Derive available months from all receipts for the month filter
  const vaultMonths = React.useMemo(() => {
    const months = new Set(receipts.map(r => r.date?.slice(0, 7)).filter(Boolean));
    return [...months].sort().reverse();
  }, [receipts]);

  // Apply vault filters
  const filteredReceipts = React.useMemo(() => {
    return receipts.filter(r => {
      if (vaultMonth !== "all" && !r.date?.startsWith(vaultMonth)) return false;
      if (vaultType !== "all" && r.type !== vaultType) return false;
      if (vaultCat !== "all" && r.category !== vaultCat) return false;
      if (vaultSearch) {
        const q = vaultSearch.toLowerCase();
        if (!(r.vendor || "").toLowerCase().includes(q) && !(r.note || "").toLowerCase().includes(q)) return false;
      }
      return true;
    });
  }, [receipts, vaultMonth, vaultType, vaultCat, vaultSearch]);

  const hasVaultFilters = vaultSearch || vaultMonth !== "all" || vaultType !== "all" || vaultCat !== "all";
  const clearVaultFilters = () => { setVaultSearch(""); setVaultMonth("all"); setVaultType("all"); setVaultCat("all"); };

  // All categories that appear in receipts (for the category filter dropdown)
  const vaultCatOptions = React.useMemo(() => {
    const seen = new Set(receipts.map(r => r.category).filter(Boolean));
    return [...seen].map(id => CB.catById(id)).filter(Boolean).sort((a, b) => a.name.localeCompare(b.name));
  }, [receipts]);

  const fileInputRef = React.useRef(null);

  const reset = () => {
    setUploadedFile(null);
    setParsed(null);
    setError(null);
    setScanning(false);
    setScanStatus("");
    setPasteText("");
    setShowPaste(false);
    setPendingReceiptId(null);
    setPotentialMatches(null);
    setLinking(null);
    // Clear file input so re-selecting the same file triggers onChange
    if (fileInputRef.current) fileInputRef.current.value = "";
  };

  const findMatches = async (parsedData) => {
    if (!window.CB_API || !parsedData?.amount || !parsedData?.date) { setPotentialMatches([]); return; }
    try {
      const res = await window.CB_API.getReceiptMatches({
        amount: parsedData.amount,
        vendor: parsedData.vendor || "",
        date: parsedData.date,
      });
      setPotentialMatches(res.matches ?? []);
    } catch { setPotentialMatches([]); }
  };

  const handleFile = async (file) => {
    if (!file) return;
    reset();
    setScanning(true);
    setScanStatus("Reading file…");

    try {
      const isPdf = file.type === "application/pdf" || /\.pdf$/i.test(file.name);
      const isImage = file.type.startsWith("image/");

      // Extract text for AI — never throw here, always fall through to upload
      let extractedText = "";
      if (isPdf) {
        setScanStatus("Extracting text from PDF…");
        try {
          extractedText = await extractPdfText(file);
        } catch (_) {
          // pdf.js failed (scanned image, corrupted, etc.) — upload anyway
          extractedText = "";
        }
        // If no text, treat like an image and let AI infer from filename
        if (!extractedText.trim()) {
          extractedText = `[PDF file uploaded: "${file.name}", size ${(file.size / 1024).toFixed(0)} KB. No embedded text — infer fields from filename and common receipt patterns.]`;
        }
      } else if (isImage) {
        setScanStatus("Reading image with AI…");
        extractedText = `[Image file uploaded: "${file.name}", size ${(file.size / 1024).toFixed(0)} KB. No raw text available — use the filename as a hint and infer typical receipt fields. If you can't tell anything specific, mark confidence "low" and leave fields empty.]`;
      } else {
        setScanStatus("Reading text file…");
        try { extractedText = await file.text(); } catch (_) { extractedText = ""; }
      }

      setScanStatus("Uploading & asking AI to parse…");

      if (window.CB_API) {
        // Send file + text to backend — backend handles Supabase upload + AI parse
        const fd = new FormData();
        fd.append("file", file);
        if (extractedText) fd.append("text", extractedText.slice(0, 8000));

        let res = await window.CB_API.uploadReceipt(fd);

        // ── Exact duplicate: same file already uploaded ──
        if (res?.duplicate && res?.duplicateType === "exact") {
          setScanning(false);
          setScanStatus("");
          setError(null);
          const proceed = confirm(
            res.message + "\n\nDo you want to upload it again anyway?"
          );
          if (!proceed) return;
          // Re-upload with force flag
          setScanning(true);
          setScanStatus("Re-uploading (forced)…");
          fd.append("force", "true");
          res = await window.CB_API.uploadReceipt(fd);
        }

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

        // ── Fuzzy duplicate warning: same vendor + amount + date ──
        if (res.duplicateWarning) {
          const skip = !confirm(
            res.duplicateWarning.message + "\n\nDo you still want to keep this receipt?"
          );
          if (skip) {
            // Delete the just-uploaded receipt
            try { await window.CB_API.deleteReceiptAndTx(res.receiptId); } catch {}
            setScanning(false);
            setScanStatus("");
            return;
          }
        }

        // Build local preview for images (avoids an extra network round-trip)
        let previewUrl = res.previewUrl;
        if (isImage && !previewUrl) {
          previewUrl = await new Promise((resolve, reject) => {
            const r = new FileReader();
            r.onload = () => resolve(r.result);
            r.onerror = reject;
            r.readAsDataURL(file);
          });
        }

        setUploadedFile({
          name: file.name,
          mimeType: res.mimeType || file.type,
          size: file.size,
          previewUrl,
          isPdf, isImage,
        });
        setPendingReceiptId(res.receiptId);
        if (res.parsed) {
          setParsed(res.parsed);
          findMatches(res.parsed);
        } else setError("AI couldn't parse this receipt — fill in the fields manually.");
      } else {
        // Backend offline — fall back to text-only parse via old endpoint
        setError("Not connected to backend. Make sure you're logged in.");
      }

      setScanning(false);
      setScanStatus("");
    } catch (e) {
      console.error("[Receipt upload]", e);
      const msg = e?.data?.error || e?.message || "Couldn't process that file.";
      setError(
        msg.includes("ANTHROPIC_API_KEY") || msg.includes("auth")
          ? "AI parsing isn't set up yet — ask the admin to add the ANTHROPIC_API_KEY to the backend."
          : msg.includes("Backend offline") || msg.includes("logged in")
          ? "Not connected to backend. Make sure you're logged in."
          : msg
      );
      setScanning(false);
      setScanStatus("");
    }
  };

  // Paste-text fallback (no file upload, just text parse)
  const scanPasteText = async () => {
    if (!pasteText.trim() || scanning) return;
    setScanning(true);
    setScanStatus("Asking AI to parse…");
    setError(null);
    setParsed(null);
    try {
      if (!window.CB_API) throw new Error("Backend offline");
      const res = await window.CB_API.parseReceipt({ text: pasteText, filename: "pasted-receipt", isImage: false });
      if (!res?.ok || !res?.parsed) throw new Error(res?.error || "Empty response");
      setParsed(res.parsed);
      findMatches(res.parsed);
    } catch (e) {
      setError(e?.data?.error || e?.message || "Parse failed");
    }
    setScanning(false);
    setScanStatus("");
  };

  const saveAsTransaction = async () => {
    if (!parsed) return;
    // Guard against null fields that would fail backend validation
    const today = new Date().toISOString().slice(0, 10);
    const safeDate = parsed.date && /^\d{4}-\d{2}-\d{2}$/.test(parsed.date) ? parsed.date : today;
    const safeAmount = typeof parsed.amount === "number" && parsed.amount > 0 ? parsed.amount : 0;
    const safeVendor = parsed.vendor?.trim() || "Unknown vendor";
    const safeType = parsed.type === "income" ? "income" : "expense";
    try {
      if (window.CB_API) {
        const safeCurrency = parsed.currency && window.CB_DATA?.currencies?.[parsed.currency]
          ? parsed.currency : "USD";
        const saved = await window.CB_API.createTransaction({
          date: safeDate,
          type: safeType,
          categoryId: parsed.suggested_category || null,
          vendor: safeVendor,
          amount: safeAmount,
          currency: safeCurrency,
          note: parsed.note || "",
          status: "cleared",
          hasReceipt: true,
          receiptId: pendingReceiptId || null,
        });
        setTxState(ts => [{ ...saved, receipt: true }, ...ts]);
        setReceiptTxs(prev => [{ ...saved, receipt: true }, ...(prev ?? [])]);
      } else {
        const tx = {
          id: "tx_receipt_" + Date.now(),
          date: safeDate,
          type: safeType,
          category: parsed.suggested_category,
          vendor: safeVendor,
          amount: safeAmount,
          currency: parsed.currency || "USD",
          account: "card",
          note: parsed.note || "",
          status: "cleared",
          receipt: true,
          receiptId: pendingReceiptId || null,
        };
        setTxState(ts => [tx, ...ts]);
      }
      reset();
      setTab("vault");
    } catch(e) {
      window.toast?.({ type: "error", message: "Failed to save transaction" });
    }
  };

  const linkToExisting = async (txId) => {
    if (!pendingReceiptId) return;
    setLinking(txId);
    try {
      const updated = await window.CB_API.updateTransaction(txId, {
        receiptId: pendingReceiptId,
        hasReceipt: true,
      });
      setReceiptTxs(prev => [{ ...updated, receipt: true }, ...(prev ?? [])]);
      setTxState(ts => ts.map(t => t.id === txId ? { ...t, receiptId: pendingReceiptId, receipt: true, hasReceipt: true } : t));
      window.toast?.({ message: `Receipt linked to "${updated.vendor || 'transaction'}"` });
      reset();
      setTab("vault");
    } catch (e) {
      window.toast?.({ type: "error", message: e.message || "Failed to link receipt" });
    }
    setLinking(null);
  };

  const handleDeleteReceipt = async (tx) => {
    try {
      if (window.CB_API) {
        if (tx.receiptId) {
          // Deletes receipt file + DB record + linked transaction
          await window.CB_API.deleteReceiptAndTx(tx.receiptId);
        } else {
          // No stored file — just delete the transaction
          await window.CB_API.deleteTransaction(tx.id);
        }
      }
      setTxState(ts => ts.filter(t => t.id !== tx.id));
      setViewing(null);
    } catch(e) {
      window.toast?.({ type: "error", message: "Failed to delete receipt" });
    }
  };

  return (
    <div className="page-fade">
      <div className="page-head">
        <div>
          <h1 className="page-title">Receipts</h1>
          <div className="page-sub">
            {hasVaultFilters ? `${filteredReceipts.length} of ${receipts.length}` : receipts.length} on file · upload + AI scan
          </div>
        </div>
        <div className="page-actions">
          <button className="btn btn-primary" onClick={() => setTab("scan")}>
            <Icon name="upload" size={14} /> Upload & scan
          </button>
        </div>
      </div>

      <div className="segmented" style={{ marginBottom: 18 }}>
        <button className={tab === "vault" ? "on" : ""} onClick={() => setTab("vault")}>
          Vault · {receipts.length}
        </button>
        <button className={tab === "scan" ? "on" : ""} onClick={() => setTab("scan")}>
          Upload & scan
        </button>
      </div>

      {tab === "vault" && (
        <>
          <div className="filters" style={{ marginBottom: 18 }}>
            <div className="search">
              <Icon name="search" size={15} color="var(--muted)" />
              <input
                placeholder="Search vendor…"
                value={vaultSearch}
                onChange={e => setVaultSearch(e.target.value)}
              />
            </div>
            <div className="segmented">
              <button className={vaultType === "all" ? "on" : ""} onClick={() => setVaultType("all")}>All</button>
              <button className={vaultType === "income" ? "on" : ""} onClick={() => setVaultType("income")}>Income</button>
              <button className={vaultType === "expense" ? "on" : ""} onClick={() => setVaultType("expense")}>Expenses</button>
            </div>
            <select className="input" style={{ width: "auto", padding: "7px 12px" }}
              value={vaultMonth} onChange={e => setVaultMonth(e.target.value)}>
              <option value="all">All months</option>
              {vaultMonths.map(m => <option key={m} value={m}>{CB.monthLabel(m)}</option>)}
            </select>
            <select className="input" style={{ width: "auto", padding: "7px 12px" }}
              value={vaultCat} onChange={e => setVaultCat(e.target.value)}>
              <option value="all">All categories</option>
              {vaultCatOptions.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
            </select>
            {hasVaultFilters && (
              <button className="btn btn-ghost btn-sm" onClick={clearVaultFilters}>
                Clear filters
              </button>
            )}
          </div>
          <ReceiptVault receipts={filteredReceipts} onPick={setViewing} />
        </>
      )}

      {tab === "scan" && (
        <ReceiptScan
          uploadedFile={uploadedFile}
          scanning={scanning}
          scanStatus={scanStatus}
          parsed={parsed}
          setParsed={setParsed}
          error={error}
          onFile={handleFile}
          onSave={saveAsTransaction}
          onReset={reset}
          fileInputRef={fileInputRef}
          pasteText={pasteText}
          setPasteText={setPasteText}
          showPaste={showPaste}
          setShowPaste={setShowPaste}
          onScanPaste={scanPasteText}
          potentialMatches={potentialMatches}
          onLink={linkToExisting}
          linking={linking}
        />
      )}

      <ReceiptPreviewModal
        tx={viewing}
        onClose={() => setViewing(null)}
        goTo={goTo}
        onDelete={handleDeleteReceipt}
      />
    </div>
  );
};

// ── Preview modal — shows the stored receipt file + extracted data ─────────────
const ReceiptPreviewModal = ({ tx, onClose, goTo, onDelete }) => {
  const [fileUrl, setFileUrl] = React.useState(null);
  const [fileMime, setFileMime] = React.useState(null);
  const [fileLoading, setFileLoading] = React.useState(false);
  const [deleting, setDeleting] = React.useState(false);

  React.useEffect(() => {
    if (!tx) { setFileUrl(null); setFileMime(null); return; }
    if (!tx.receiptId) return;
    setFileLoading(true);
    window.CB_API?.getReceiptUrl(tx.receiptId)
      .then(res => {
        setFileUrl(res.url);
        setFileMime(res.mimeType);
      })
      .catch(err => console.warn("[Receipt URL]", err))
      .finally(() => setFileLoading(false));
  }, [tx?.receiptId]);

  if (!tx) return null;

  const cat = CB.catById(tx.category);
  const acc = CB.accountById(tx.account);
  const isPdf = fileMime === "application/pdf" || (fileMime && fileMime.includes("pdf"));

  const handleDelete = async () => {
    if (!confirm("Delete this receipt and its transaction? This cannot be undone.")) return;
    setDeleting(true);
    await onDelete(tx);
    setDeleting(false);
  };

  return (
    <Modal open={!!tx} onClose={onClose}
      maxWidth={780}
      title={tx.vendor || "Receipt"}
      subtitle={new Date(tx.date).toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" })}
      footer={
        <>
          <button className="btn btn-danger" onClick={handleDelete} disabled={deleting}
            style={{ marginRight: "auto" }}>
            {deleting ? <Spinner /> : <Icon name="trash" size={12} />}
            {deleting ? " Deleting…" : " Delete"}
          </button>
          <button className="btn" onClick={onClose}>Close</button>
          <button className="btn btn-primary" onClick={() => {
            window.__openTxId = tx.id;
            onClose();
            goTo("ledger");
          }}>
            <Icon name="ledger" size={12} /> Open transaction
          </button>
        </>
      }>
      <div style={{ display: "grid", gridTemplateColumns: "1.1fr 1fr", gap: 16 }}>
        {/* File preview pane */}
        <div style={{
          borderRadius: 12, overflow: "hidden",
          background: "var(--surface-2)",
          border: "1px solid var(--hairline)",
          minHeight: 340, position: "relative",
          display: "flex", alignItems: "center", justifyContent: "center",
        }}>
          {fileLoading ? (
            <div style={{ textAlign: "center", color: "var(--muted)" }}>
              <Spinner large />
              <div style={{ marginTop: 10, fontSize: 12 }}>Loading file…</div>
            </div>
          ) : fileUrl && isPdf ? (
            <iframe
              src={fileUrl}
              title="Receipt PDF"
              style={{ width: "100%", height: 460, border: 0, display: "block" }}
            />
          ) : fileUrl ? (
            <img src={fileUrl} alt="Receipt"
              style={{ maxWidth: "100%", maxHeight: 460, display: "block" }} />
          ) : (
            <div style={{ textAlign: "center", padding: 28 }}>
              <div style={{
                width: 84, height: 110, borderRadius: 10,
                background: `linear-gradient(135deg, ${cat?.color || "var(--surface)"}, oklch(0.5 0.1 30))`,
                color: "white", display: "grid", placeItems: "center",
                margin: "0 auto 14px",
                fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 700,
              }}>
                <Icon name="receipt" size={28} />
              </div>
              <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 6, lineHeight: 1.5 }}>
                {tx.receiptId
                  ? "File preview unavailable"
                  : "No file stored for this receipt."}
              </div>
            </div>
          )}
        </div>

        {/* Details pane */}
        <div>
          <div style={{ textAlign: "center", padding: "8px 0 14px", borderBottom: "1px solid var(--hairline)" }}>
            <div className="bignum" style={{ color: tx.type === "income" ? "var(--income-text)" : "var(--ink)" }}>
              {tx.type === "income" ? "+" : "−"}{CB.fmtCur(tx.amount, tx.currency || "USD")}
            </div>
            <div style={{ marginTop: 6 }}>
              <Pill kind={tx.type === "income" ? "income" : "expense"}>
                <span className="cat-swatch" style={{ background: cat?.color, width: 7, height: 7 }} />
                {cat?.name}
              </Pill>
            </div>
          </div>

          <div style={{ display: "grid", gridTemplateColumns: "100px 1fr", gap: 10, padding: "16px 0", fontSize: 13 }}>
            <div style={{ color: "var(--muted)" }}>Date</div>
            <div>{new Date(tx.date).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })}</div>
            <div style={{ color: "var(--muted)" }}>Account</div>
            <div>{acc?.name || "—"}</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>

          {fileUrl && (
            <a className="btn btn-ghost btn-sm" href={fileUrl} target="_blank" rel="noreferrer"
              style={{ width: "100%", justifyContent: "center", marginTop: 8, display: "flex", gap: 6 }}>
              <Icon name="download" size={12} /> Open original
            </a>
          )}
        </div>
      </div>
    </Modal>
  );
};

// ── PDF text extraction via pdf.js ─────────────────────────────────────────────
async function extractPdfText(file) {
  if (!window.pdfjsLib) throw new Error("PDF library not loaded.");
  const buf = await file.arrayBuffer();
  const pdf = await window.pdfjsLib.getDocument({ data: buf }).promise;
  let out = "";
  for (let p = 1; p <= Math.min(pdf.numPages, 5); p++) {
    const page = await pdf.getPage(p);
    const content = await page.getTextContent();
    out += content.items.map(it => it.str).join(" ") + "\n";
  }
  return out;
}

// ── Vault grid ─────────────────────────────────────────────────────────────────
const ReceiptVault = ({ receipts, onPick }) => {
  if (receipts.length === 0) {
    return (
      <EmptyState
        icon="receipt"
        title="No receipts yet"
        subtitle="Drop a PDF, photo, or document into the AI scan tab — we'll read it and offer to save as a transaction."
      />
    );
  }
  return (
    <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
      {receipts.slice(0, 24).map(tx => {
        const cat = CB.catById(tx.category);
        return (
          <button key={tx.id} className="card" onClick={() => onPick(tx)}
            style={{
              padding: 16, cursor: "pointer", border: 0, textAlign: "left",
              fontFamily: "inherit", color: "inherit",
              transition: "transform 0.08s, box-shadow 0.15s",
            }}
            onMouseDown={(e) => e.currentTarget.style.transform = "scale(0.985)"}
            onMouseUp={(e) => e.currentTarget.style.transform = ""}
            onMouseLeave={(e) => e.currentTarget.style.transform = ""}>
            <div style={{
              height: 110, borderRadius: 8,
              background: `linear-gradient(135deg, ${cat?.color || "var(--surface-2)"}, oklch(0.5 0.1 30))`,
              position: "relative", marginBottom: 12,
              display: "grid", placeItems: "center", color: "white",
            }}>
              <Icon name="receipt" size={28} />
              {tx.receiptId && (
                <span style={{
                  position: "absolute", bottom: 8, right: 8,
                  background: "oklch(0 0 0 / 0.45)", color: "white",
                  fontSize: 10, padding: "2px 7px", borderRadius: 6,
                  backdropFilter: "blur(2px)",
                }}>
                  file stored
                </span>
              )}
            </div>
            <div style={{ fontWeight: 500, fontSize: 14, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
              {tx.vendor}
            </div>
            <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 2 }}>
              {new Date(tx.date).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
            </div>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 10 }}>
              <Pill kind={tx.type === "income" ? "income" : "expense"}>
                <span className="cat-swatch" style={{ background: cat?.color, width: 7, height: 7 }} />
                {cat?.name}
              </Pill>
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 500 }}>
                {CB.fmtCur(tx.amount, tx.currency || "USD")}
              </span>
            </div>
          </button>
        );
      })}
    </div>
  );
};

// ── Scan panel ─────────────────────────────────────────────────────────────────
const ReceiptScan = ({ uploadedFile, scanning, scanStatus, parsed, setParsed, error, onFile, onSave, onReset, pasteText, setPasteText, showPaste, setShowPaste, onScanPaste, fileInputRef, potentialMatches, onLink, linking }) => {
  const setField = (key, val) => setParsed(p => ({ ...p, [key]: val }));
  const ALL_CATS = ["software","equipment","contractors","travel","music","marketing","office","meals","fees","other","adsense","sponsorship","brand","merch","affiliate","members"];
  // Filter categories to match the selected type — derive from CB data so it stays in sync
  const activeCats = React.useMemo(() => {
    const type = parsed?.type || "expense";
    return ALL_CATS.filter(id => {
      const cat = CB.catById(id);
      if (!cat) return false;
      return cat.kind === type;
    });
  }, [parsed?.type]);
  const inputRef = fileInputRef || React.useRef(null);
  const [dragging, setDragging] = React.useState(false);

  const onDrop = (e) => {
    e.preventDefault();
    setDragging(false);
    const f = e.dataTransfer.files?.[0];
    if (f) onFile(f);
  };

  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
      {/* Upload panel */}
      <div className="card">
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 12 }}>
          <Icon name="upload" size={18} />
          <div style={{ fontFamily: "var(--font-display)", fontSize: 22 }}>Upload a receipt</div>
        </div>
        <p style={{ fontSize: 13, color: "var(--muted)", marginTop: 0, marginBottom: 14 }}>
          PDF, photo, or any document. We'll read it, extract the fields, and offer to save as a transaction.
        </p>

        {!uploadedFile && !scanning && (
          <>
            <input ref={inputRef} type="file"
              accept="application/pdf,image/*,.heic,.heif"
              style={{ display: "none" }}
              onChange={(e) => onFile(e.target.files?.[0])} />

            <button
              onClick={() => inputRef.current?.click()}
              onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
              onDragLeave={() => setDragging(false)}
              onDrop={onDrop}
              style={{
                width: "100%",
                padding: "40px 20px",
                borderRadius: 16,
                background: dragging ? "var(--primary-soft)" : "var(--surface-2)",
                border: "2px dashed " + (dragging ? "var(--primary)" : "var(--hairline-strong)"),
                color: dragging ? "var(--primary-ink)" : "var(--ink)",
                cursor: "pointer",
                display: "flex", flexDirection: "column", alignItems: "center", gap: 10,
                transition: "background 0.15s, border-color 0.15s",
              }}>
              <div style={{
                width: 56, height: 56, borderRadius: 14,
                background: "var(--surface)",
                color: "var(--primary)",
                display: "grid", placeItems: "center",
              }}>
                <Icon name="upload" size={26} />
              </div>
              <div style={{ fontWeight: 500, fontSize: 15 }}>
                {dragging ? "Drop to upload" : "Click to upload or drag a file here"}
              </div>
              <div style={{ fontSize: 12, color: "var(--muted)" }}>
                PDF · PNG · JPG · HEIC · up to ~10 MB
              </div>
            </button>

            <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>
                For PDFs we extract the text directly. For photos we use AI vision to read amounts,
                dates, and vendors. Files are securely stored in your receipt vault.
              </div>
            </div>

            {/* Paste-text fallback */}
            <div style={{ marginTop: 14 }}>
              <button
                className="btn btn-ghost btn-sm"
                onClick={() => setShowPaste(s => !s)}
                style={{ width: "100%", justifyContent: "center", fontSize: 12 }}>
                {showPaste ? "Hide" : "Or paste text manually"}
              </button>
              {showPaste && (
                <div style={{ marginTop: 8 }}>
                  <textarea
                    className="input"
                    style={{ width: "100%", minHeight: 120, fontSize: 12, resize: "vertical", boxSizing: "border-box" }}
                    placeholder={"Paste the invoice or receipt text here…\n\nVendor: Acme Corp\nDate: 2025-05-01\nTotal: $149.00\n…"}
                    value={pasteText}
                    onChange={e => setPasteText(e.target.value)}
                  />
                  <button
                    className="btn btn-primary"
                    style={{ marginTop: 8, width: "100%", justifyContent: "center" }}
                    disabled={!pasteText.trim() || scanning}
                    onClick={onScanPaste}>
                    <Icon name="sparkles" size={13} /> Parse with AI
                  </button>
                </div>
              )}
            </div>
          </>
        )}

        {/* Uploaded file preview */}
        {uploadedFile && (
          <div>
            <div style={{
              borderRadius: 12, overflow: "hidden",
              border: "1px solid var(--hairline)",
              background: "var(--surface-2)",
              minHeight: 240,
              display: "flex", alignItems: "center", justifyContent: "center",
              position: "relative",
            }}>
              {uploadedFile.previewUrl && uploadedFile.isPdf ? (
                <iframe
                  src={uploadedFile.previewUrl}
                  title={uploadedFile.name}
                  style={{ width: "100%", height: 360, border: 0, display: "block" }}
                />
              ) : uploadedFile.previewUrl ? (
                <img src={uploadedFile.previewUrl} alt={uploadedFile.name}
                  style={{ maxWidth: "100%", maxHeight: 360, display: "block" }} />
              ) : (
                <div style={{ textAlign: "center", padding: 32 }}>
                  <div style={{
                    width: 64, height: 80, borderRadius: 8,
                    background: "var(--surface)",
                    border: "1px solid var(--hairline-strong)",
                    margin: "0 auto 14px",
                    display: "grid", placeItems: "center",
                    color: "var(--ink-soft)",
                    fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 700,
                  }}>PDF</div>
                  <div style={{ fontWeight: 500, fontSize: 13 }}>{uploadedFile.name}</div>
                  <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 4 }}>
                    {(uploadedFile.size / 1024).toFixed(0)} KB · text extracted
                  </div>
                </div>
              )}
            </div>
            <div style={{ marginTop: 12, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              <div style={{ fontSize: 12, color: "var(--muted)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                <Icon name="file" size={12} /> {uploadedFile.name}
              </div>
              <button className="btn btn-ghost btn-sm" onClick={onReset}>
                <Icon name="close" size={11} /> Remove
              </button>
            </div>
          </div>
        )}

        {scanning && !uploadedFile && (
          <div style={{ padding: "40px 20px", textAlign: "center" }}>
            <Spinner large />
            <div style={{ marginTop: 14, fontSize: 13, color: "var(--ink-soft)" }}>
              {scanStatus || "Working…"}
            </div>
          </div>
        )}
      </div>

      {/* Extracted panel */}
      <div className="card">
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 12 }}>
          <Icon name="sparkles" size={18} />
          <div style={{ fontFamily: "var(--font-display)", fontSize: 22 }}>Extracted</div>
        </div>

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

        {!parsed && !error && !scanning && (
          <div style={{ padding: "40px 20px", textAlign: "center", color: "var(--muted)", fontSize: 13 }}>
            <Icon name="file" size={32} />
            <div style={{ marginTop: 12 }}>Upload a receipt to see extracted fields here.</div>
          </div>
        )}

        {scanning && (
          <div style={{ padding: "40px 20px", textAlign: "center", color: "var(--muted)" }}>
            <Spinner large />
            <div style={{ marginTop: 14, fontSize: 13 }}>{scanStatus || "Working…"}</div>
          </div>
        )}

        {parsed && !scanning && (
          <>
            <div style={{ fontSize: 11, color: "var(--muted)", marginBottom: 10, display: "flex", alignItems: "center", gap: 5 }}>
              <Icon name="sparkles" size={11} /> AI extracted — edit anything before saving
            </div>

            <div style={{ display: "grid", gridTemplateColumns: "90px 1fr", gap: "10px 12px", fontSize: 13, alignItems: "center" }}>
              <label style={{ color: "var(--muted)" }}>Vendor</label>
              <input
                className="input input-sm"
                value={parsed.vendor || ""}
                onChange={e => setField("vendor", e.target.value)}
                placeholder="Vendor name"
              />

              <label style={{ color: "var(--muted)" }}>Date</label>
              <input
                className="input input-sm"
                type="date"
                value={parsed.date || ""}
                onChange={e => setField("date", e.target.value)}
              />

              <label style={{ color: "var(--muted)" }}>Amount</label>
              <div style={{ display: "flex", gap: 6 }}>
                <input
                  className="input input-sm"
                  type="number"
                  min="0"
                  step="0.01"
                  value={parsed.amount ?? ""}
                  onChange={e => setField("amount", parseFloat(e.target.value) || 0)}
                  placeholder="0.00"
                  style={{ flex: 1, minWidth: 0 }}
                />
                <select
                  className="input input-sm"
                  value={parsed.currency || "USD"}
                  onChange={e => setField("currency", e.target.value)}
                  style={{ width: 80, flexShrink: 0 }}>
                  {Object.values(window.CB_DATA?.currencies || { USD: { code: "USD" } }).map(c => (
                    <option key={c.code} value={c.code}>{c.code}</option>
                  ))}
                </select>
              </div>

              <label style={{ color: "var(--muted)" }}>Type</label>
              <select
                className="input input-sm"
                value={parsed.type || "expense"}
                onChange={e => { setField("type", e.target.value); setField("suggested_category", ""); }}>
                <option value="expense">Expense</option>
                <option value="income">Income</option>
              </select>

              <label style={{ color: "var(--muted)" }}>Category</label>
              <select
                className="input input-sm"
                value={parsed.suggested_category || ""}
                onChange={e => setField("suggested_category", e.target.value)}>
                <option value="">— pick one —</option>
                {activeCats.map(c => {
                  const cat = CB.catById(c);
                  return <option key={c} value={c}>{cat?.name || c}</option>;
                })}
              </select>

              <label style={{ color: "var(--muted)" }}>Note</label>
              <input
                className="input input-sm"
                value={parsed.note || ""}
                onChange={e => setField("note", e.target.value)}
                placeholder="Optional note"
              />
            </div>

            {parsed.items && parsed.items.length > 1 && (
              <div style={{ marginTop: 14, padding: "10px 12px", background: "var(--surface-2)", borderRadius: 8, fontSize: 12 }}>
                {parsed.items.map((it, i) => (
                  <div key={i} style={{ display: "flex", justifyContent: "space-between", padding: "2px 0", color: "var(--ink-soft)" }}>
                    <span>{it.desc}</span>
                    <span style={{ fontFamily: "var(--font-mono)" }}>{CB.fmtCur(it.amount, parsed.currency || "USD")}</span>
                  </div>
                ))}
              </div>
            )}

            <div style={{ marginTop: 4, display: "flex", alignItems: "center", gap: 6 }}>
              <Pill kind={parsed.confidence === "high" ? "income" : parsed.confidence === "low" ? "expense" : ""} style={{ fontSize: 11 }}>
                {parsed.confidence || "medium"} confidence
              </Pill>
            </div>

            {potentialMatches && potentialMatches.length > 0 && (
              <div style={{ marginTop: 16, padding: 12, borderRadius: 10, background: "var(--surface-2)", border: "1px solid var(--hairline)" }}>
                <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 8, display: "flex", alignItems: "center", gap: 6 }}>
                  <Icon name="link" size={12} />
                  Link to existing transaction instead?
                </div>
                {potentialMatches.map(m => (
                  <div key={m.id} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "7px 0", borderTop: "1px solid var(--hairline)", gap: 8 }}>
                    <div style={{ fontSize: 12, minWidth: 0 }}>
                      <div style={{ fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{m.vendor}</div>
                      <div style={{ color: "var(--muted)", fontSize: 11 }}>{m.date} · {CB.fmtCur(m.amount, m.currency || "USD")}</div>
                    </div>
                    <button
                      className="btn btn-sm btn-primary"
                      style={{ flexShrink: 0, fontSize: 12 }}
                      disabled={!!linking}
                      onClick={() => onLink(m.id)}>
                      {linking === m.id ? <Spinner /> : "Link"}
                    </button>
                  </div>
                ))}
                <div style={{ marginTop: 8, fontSize: 11, color: "var(--muted)" }}>
                  Or save as a new transaction below
                </div>
              </div>
            )}
            <button className="btn btn-primary" onClick={onSave} style={{ marginTop: 16, width: "100%", justifyContent: "center" }}>
              <Icon name="check" size={14} /> Add to vault
            </button>
          </>
        )}
      </div>
    </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",
  }} />
);

if (!document.getElementById("cb-spin-anim")) {
  const s = document.createElement("style");
  s.id = "cb-spin-anim";
  s.textContent = "@keyframes spin { to { transform: rotate(360deg); } }";
  document.head.appendChild(s);
}

window.Receipts = Receipts;
