// detail.jsx — Transaction detail + Receipt detail screens

const { useState: dState, useMemo } = React;

// ────────────────────────────────────────────────────────────────────────────
// Shared write helper — optimistic patch a transaction, call the Phase 4 API,
// reconcile with the server-returned row, roll back + error-toast on failure.
// Used by both detail screens and (indirectly) the pair flow. `apiPatch` is
// the body sent to ViewerAPI.patchTransaction (category_id / needs_review /
// note / receipt_id); `optimistic` is the immediate viewer-shape patch.
// ────────────────────────────────────────────────────────────────────────────
function patchTxnLive(setDb, txnList, id, optimistic, apiPatch, push, okMsg) {
  const prevRow = txnList.find(t => t.id === id);
  setDb(prev => ({
    ...prev,
    transactions: prev.transactions.map(t => t.id === id ? { ...t, ...optimistic } : t),
  }));
  ViewerAPI.patchTransaction(id, apiPatch)
    .then(resp => {
      const server = resp && resp.transaction;
      if (server) {
        setDb(prev => ({
          ...prev,
          transactions: prev.transactions.map(t => t.id === id ? { ...t, ...server } : t),
        }));
      }
      if (okMsg) push(okMsg);
    })
    .catch(err => {
      setDb(prev => ({
        ...prev,
        transactions: prev.transactions.map(t => t.id === id ? { ...t, ...prevRow } : t),
      }));
      push("Couldn't save: " + err.message);
    });
}

// ────────────────────────────────────────────────────────────────────────────
// Transaction Detail (shows paired receipt inline)
// ────────────────────────────────────────────────────────────────────────────
function TransactionDetailScreen({ db, setDb, navTo, txnId, push }) {
  const t = db.transactions.find(x => x.id === txnId);
  // Hooks first, unconditionally (Rules of Hooks) — the not-found guard below
  // must come AFTER every hook call. noteDraft's seed tolerates a null t.
  const [pairing, setPairing] = dState(false);
  const [editingNote, setEditingNote] = dState(false);
  const [noteDraft, setNoteDraft] = dState((t && t.note) || "");

  if (!t) {
    return <div className="muted">Transaction not found. <a onClick={() => navTo({screen:"transactions"})}>← Back</a></div>;
  }
  const r = t.receiptId ? db.receipts.find(x => x.id === t.receiptId) : null;

  // category / needs_review / note edits all go through patchTxnLive.
  const updateTxn = (optimistic, apiPatch, okMsg) =>
    patchTxnLive(setDb, db.transactions, t.id, optimistic, apiPatch, push, okMsg);

  // Pair / unpair — Phase 4 backend accepts receipt_id in the transaction
  // patch (receipt_id pairs, null unpairs; pairing also clears needs_review
  // server-side). Decision 10.1: in scope.
  const pairReceipt = (rid) => {
    updateTxn({ receiptId: rid, needsReview: false }, { receipt_id: rid }, "Paired to receipt");
  };
  const unpairReceipt = () => {
    updateTxn({ receiptId: null }, { receipt_id: null }, "Unpaired receipt");
  };

  const commitNote = () => {
    const next = noteDraft.trim();
    setEditingNote(false);
    if (next === (t.note || "")) return;        // no change
    updateTxn({ note: next }, { note: next }, "Note updated");
  };

  return (
    <div className="stack">
      {/* Header card */}
      <div className="card">
        <div className="card-bd">
          <div className="row between" style={{ alignItems: "flex-start", flexWrap: "wrap", gap: 16 }}>
            <div style={{ minWidth: 0 }}>
              <div className="row" style={{ gap: 6, marginBottom: 8 }}>
                <PairedPill paired={!!r} />
                {t.needsReview && <ReviewPill needsReview />}
                {t.source !== "bank" && <Pill kind="info">{t.source.toUpperCase()}</Pill>}
                <Pill kind="muted">{t.id}</Pill>
              </div>
              <div className="huge">{t.description}</div>
              <div className="tiny muted" style={{ marginTop: 4 }}>{fmtDateLong(t.date)}</div>
            </div>
            <div style={{ textAlign: "right" }}>
              <div className="label-up">Amount</div>
              <div className={"huge amt " + (t.amount < 0 ? "neg" : "pos")} style={{ marginTop: 2 }}>
                {(t.amount >= 0 ? "+" : "−") + fmt(Math.abs(t.amount))}
              </div>
              <div className="tiny muted">{db.cat(t.category).schedC}</div>
            </div>
          </div>

          <hr className="div" style={{ margin: "16px 0" }} />

          <dl className="kv" style={{ gridTemplateColumns: "140px 1fr 140px 1fr" }}>
            <dt>Category</dt>
            <dd>
              <InlineSelect
                value={t.category}
                options={db.categories.map(c => ({ value: c.id, label: c.label }))}
                onChange={v => updateTxn({ category: v }, { category_id: v }, "Category updated")}
              />
            </dd>
            <dt>Source</dt>
            <dd className="row" style={{ gap: 6 }}>
              <Pill kind="muted">{t.source}</Pill>
            </dd>

            <dt>Needs review</dt>
            <dd>
              <button
                className={"btn sm " + (t.needsReview ? "" : "ghost")}
                onClick={() => updateTxn(
                  { needsReview: !t.needsReview },
                  { needs_review: !t.needsReview },
                  t.needsReview ? "Cleared review flag" : "Flagged for review")}
              >
                <Icon name={t.needsReview ? "check" : "alert"} size={12} />
                {t.needsReview ? "Clear flag" : "Flag for review"}
              </button>
            </dd>
            <dt>Receipt</dt>
            <dd>
              {r
                ? <span className="row" style={{ gap: 6 }}>
                    <Pill kind="paired" icon="paperclip">{r.vendorShort}</Pill>
                    <button className="btn sm ghost" onClick={() => navTo({ screen: "receipt", id: r.id })}>Open →</button>
                    <button className="btn sm ghost" onClick={unpairReceipt}>Unpair</button>
                  </span>
                : <button className="btn sm" onClick={() => setPairing(true)}>
                    <Icon name="link" size={12} /> Pair to a receipt
                  </button>
              }
            </dd>

            <dt>Note</dt>
            <dd style={{ gridColumn: "2 / -1" }}>
              {editingNote ? (
                <>
                  <textarea
                    className="note-edit"
                    autoFocus
                    value={noteDraft}
                    onChange={e => setNoteDraft(e.target.value)}
                    onKeyDown={e => {
                      if (e.key === "Escape") { setNoteDraft(t.note || ""); setEditingNote(false); }
                      if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) commitNote();
                    }}
                    placeholder="Add a note for this transaction…"
                  />
                  <div className="note-edit-row">
                    <button className="btn sm primary" onClick={commitNote}>Save note</button>
                    <button className="btn sm ghost" onClick={() => { setNoteDraft(t.note || ""); setEditingNote(false); }}>Cancel</button>
                    <span className="tiny muted">⌘/Ctrl+Enter to save</span>
                  </div>
                </>
              ) : (
                <span
                  onClick={() => { setNoteDraft(t.note || ""); setEditingNote(true); }}
                  style={{ cursor: "text", color: t.note ? "var(--ink-2)" : "var(--ink-4)", borderBottom: "1px dashed var(--border-strong)" }}
                >
                  {t.note || "Add a note…"}
                  &nbsp;<Icon name="pencil" size={10} />
                </span>
              )}
            </dd>
          </dl>
        </div>
      </div>

      {/* Receipt inline */}
      {r ? (
        <Card title="Paired receipt"
              meta={`${r.vendorShort} · ${fmtDate(r.date)} · ${r.arrival === "phone-snap" ? "Phone snap" : "Emailed PDF"}`}
              action={
                <div className="row" style={{ gap: 8 }}>
                  {!r.sumMatches && <Pill kind="neg" icon="alert">Sum mismatch</Pill>}
                  <button className="btn sm" onClick={() => navTo({ screen: "receipt", id: r.id })}>
                    Open receipt →
                  </button>
                </div>
              }
        >
          <div className="detail-grid">
            <div style={{ display: "grid", placeItems: "center", padding: "8px 0 16px" }}>
              <FauxReceipt receipt={r} />
            </div>
            <ReceiptLineItems receipt={r} db={db} setDb={setDb} push={push} />
          </div>
        </Card>
      ) : (
        <Card title="No receipt paired" meta="This bank transaction has no matching receipt.">
          <div className="row" style={{ gap: 12, alignItems: "center", justifyContent: "space-between" }}>
            <div className="small muted">
              Pair a receipt so it shows up in the line-item view, or flag this transaction for review.
            </div>
            <button className="btn primary" onClick={() => setPairing(true)}>
              <Icon name="link" size={12} /> Pair to receipt
            </button>
          </div>
        </Card>
      )}

      {pairing && (
        <PairReceiptModal
          db={db}
          txn={t}
          onClose={() => setPairing(false)}
          onPair={(rid) => {
            pairReceipt(rid);
            setPairing(false);
          }}
        />
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────────────
// Pair modal — pick from receipts, biased to similar amount / vendor
// ────────────────────────────────────────────────────────────────────────────
function PairReceiptModal({ db, txn, onClose, onPair }) {
  const [q, setQ] = dState("");
  const candidates = useMemo(() => {
    // Receipts not paired to any other transaction.
    const usedIds = new Set(db.transactions.filter(t => t.receiptId && t.id !== txn.id).map(t => t.receiptId));
    const free = db.receipts.filter(r => !usedIds.has(r.id));
    const target = Math.abs(txn.amount);
    return free
      .map(r => {
        const dT = Math.abs(r.total - target);
        const dDays = Math.abs((parseDate(r.date) - parseDate(txn.date)) / (1000*60*60*24));
        return { r, score: dT + dDays * 4 };
      })
      .filter(({ r }) => {
        if (!q.trim()) return true;
        const qs = q.trim().toLowerCase();
        return r.vendor.toLowerCase().includes(qs) || r.vendorShort.toLowerCase().includes(qs);
      })
      .sort((a, b) => a.score - b.score)
      .slice(0, 14);
  }, [db.receipts, db.transactions, txn, q]);

  return (
    <Modal title="Pair to a receipt" onClose={onClose} wide>
      <div className="stack" style={{ gap: 12 }}>
        <div className="row" style={{ gap: 12, alignItems: "center" }}>
          <div className="muted small">Pair <strong style={{ color: "var(--ink)" }}>{txn.description}</strong> ({fmt(txn.amount)}) to:</div>
          <div className="search" style={{ marginLeft: "auto", minWidth: 220 }}>
            <Icon name="search" size={14} />
            <input className="input" placeholder="Filter by vendor…" value={q} onChange={e => setQ(e.target.value)} />
          </div>
        </div>
        <div className="card flush">
          <table className="t">
            <thead><tr>
              <th>Vendor</th><th>Date</th><th>Total</th><th className="right">|Δ|</th><th></th>
            </tr></thead>
            <tbody>
              {candidates.map(({ r, score }) => {
                const delta = r.total - Math.abs(txn.amount);
                return (
                  <tr key={r.id}>
                    <td><strong>{r.vendorShort}</strong> <span className="tiny muted">{r.id}</span></td>
                    <td className="mono">{fmtDate(r.date)}</td>
                    <td className="amt">${r.total.toFixed(2)}</td>
                    <td className="right num" style={{ color: Math.abs(delta) < 0.5 ? "var(--positive)" : "var(--ink-3)" }}>
                      ${Math.abs(delta).toFixed(2)}
                    </td>
                    <td className="right">
                      <button className="btn sm primary" onClick={() => onPair(r.id)}>Pair</button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    </Modal>
  );
}

// ────────────────────────────────────────────────────────────────────────────
// Receipt Detail
// ────────────────────────────────────────────────────────────────────────────
function ReceiptDetailScreen({ db, setDb, navTo, receiptId, push }) {
  const r = db.receipts.find(x => x.id === receiptId);
  if (!r) return <div className="muted">Receipt not found. <a onClick={() => navTo({screen:"transactions"})}>← Back</a></div>;
  const pairedTxn = db.transactions.find(t => t.receiptId === r.id);

  return (
    <div className="stack">
      <div className="card">
        <div className="card-bd">
          <div className="row between" style={{ alignItems: "flex-start", flexWrap: "wrap", gap: 16 }}>
            <div>
              <div className="row" style={{ gap: 6, marginBottom: 8 }}>
                {pairedTxn
                  ? <Pill kind="paired" icon="link">Paired · {pairedTxn.id}</Pill>
                  : <Pill kind="unpaired" icon="dot">Unpaired</Pill>
                }
                {!r.sumMatches && <Pill kind="neg" icon="alert">Sum mismatch</Pill>}
                <Pill kind="muted" icon={r.arrival === "phone-snap" ? "phone" : "mail"}>
                  {r.arrival === "phone-snap" ? "Phone snap" : "Emailed PDF"}
                </Pill>
                <Pill kind="muted">{r.id}</Pill>
              </div>
              <div className="huge">{r.vendorShort}</div>
              <div className="tiny muted" style={{ marginTop: 4 }}>{r.vendor}</div>
            </div>
            <div style={{ textAlign: "right" }}>
              <div className="label-up">Total</div>
              <div className="huge num" style={{ marginTop: 2 }}>${r.total.toFixed(2)}</div>
              <div className="tiny muted">{fmtDateLong(r.date)} · {r.paymentMethod}</div>
            </div>
          </div>

          {r.note && <div className="small muted" style={{ marginTop: 12 }}><Icon name="info" size={12} /> &nbsp;{r.note}</div>}

          {!r.sumMatches && (
            <div className="row" style={{
              background: "var(--negative-soft)", color: "var(--negative)",
              padding: "10px 14px", borderRadius: 8, gap: 10, marginTop: 14,
              border: "1px solid color-mix(in srgb, var(--negative) 30%, transparent)",
            }}>
              <Icon name="alert" />
              <div style={{ fontSize: 13 }}>
                <strong>Items don't sum to total.</strong>
                &nbsp;Computed {fmt(r.computedTotal)} vs stated {fmt(r.total)} · diff {fmtSigned(r.total - r.computedTotal)}.
              </div>
            </div>
          )}
        </div>
      </div>

      <div className="detail-grid">
        <Card title="Attachment" meta={r.arrival === "phone-snap" ? "Photo from phone" : "PDF from email"}>
          <div style={{ display: "grid", placeItems: "center", padding: "4px 0" }}>
            <FauxReceipt receipt={r} />
          </div>
          <div className="row" style={{ justifyContent: "center", marginTop: 12, gap: 8 }}>
            <button className="btn sm ghost"><Icon name="download" size={12} /> Download</button>
            <button className="btn sm ghost"><Icon name="copy" size={12} /> Copy link</button>
          </div>
        </Card>

        <div className="stack">
          <Card title="Paired bank transaction" meta={pairedTxn ? pairedTxn.id : "None"}>
            {pairedTxn ? (
              <div className="row between">
                <div>
                  <div style={{ fontWeight: 500 }}>{pairedTxn.description}</div>
                  <div className="tiny muted">{fmtDate(pairedTxn.date)} · {db.cat(pairedTxn.category).label}</div>
                </div>
                <div className="row" style={{ gap: 8 }}>
                  <div className={"amt num " + (pairedTxn.amount < 0 ? "neg" : "pos")}>
                    {(pairedTxn.amount >= 0 ? "+" : "−") + fmt(Math.abs(pairedTxn.amount))}
                  </div>
                  <button className="btn sm" onClick={() => navTo({ screen: "txn", id: pairedTxn.id })}>Open →</button>
                </div>
              </div>
            ) : (
              <div className="row between">
                <div className="muted small">No bank transaction paired yet — this receipt is unmatched.</div>
                <button className="btn sm">Find a match</button>
              </div>
            )}
          </Card>

          <Card title="Line items" meta={`${r.items.length} items, ${r.charges.length} charges`} flush>
            <ReceiptLineItems receipt={r} db={db} setDb={setDb} push={push} />
          </Card>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────────────
// Line items table (used by both detail screens)
// Phase 4 — each item row now carries an InlineSelect wired to
// ViewerAPI.patchLineItem (Decision 10.2). Items without an id (mock data)
// or without a stable id fall back to read-only.
// ────────────────────────────────────────────────────────────────────────────
function ReceiptLineItems({ receipt, db, setDb, push }) {
  const r = receipt;
  const catOptions = (db && db.categories || []).map(c => ({ value: c.id, label: c.label }));

  // Optimistic line-item re-categorize. The line item lives inside the
  // receipt's `items` array; patch it in place, call the API, reconcile with
  // the server row, roll back on failure.
  const updateLineItem = (itemId, categoryId) => {
    if (itemId == null) return;
    let prevCat;
    setDb(prev => ({
      ...prev,
      receipts: prev.receipts.map(rr => rr.id !== r.id ? rr : {
        ...rr,
        items: rr.items.map(it => {
          if (it.id !== itemId) return it;
          prevCat = it.category_id;
          return { ...it, category_id: categoryId };
        }),
      }),
    }));
    ViewerAPI.patchLineItem(itemId, categoryId)
      .then(resp => {
        const server = resp && resp.line_item;
        if (!server) return;
        setDb(prev => ({
          ...prev,
          receipts: prev.receipts.map(rr => rr.id !== r.id ? rr : {
            ...rr,
            items: rr.items.map(it => it.id === itemId
              ? { ...it, category_id: server.category_id != null ? server.category_id : it.category_id }
              : it),
          }),
        }));
        push("Line item re-categorized");
      })
      .catch(err => {
        setDb(prev => ({
          ...prev,
          receipts: prev.receipts.map(rr => rr.id !== r.id ? rr : {
            ...rr,
            items: rr.items.map(it => it.id === itemId ? { ...it, category_id: prevCat } : it),
          }),
        }));
        push("Couldn't save line item: " + err.message);
      });
  };

  return (
    <div>
      <table className="t" style={{ borderTop: "1px solid var(--border)" }}>
        <thead>
          <tr>
            <th style={{ width: 70 }}>Type</th>
            <th>Description</th>
            <th style={{ width: 200 }}>Category</th>
            <th className="right" style={{ width: 110 }}>Amount</th>
          </tr>
        </thead>
        <tbody>
          {r.items.map((it, i) => (
            <tr key={it.id != null ? "id" + it.id : "i" + i}>
              <td><Pill kind="accent">item</Pill></td>
              <td>{it.name}</td>
              <td onClick={e => e.stopPropagation()}>
                {it.id != null && setDb
                  ? <InlineSelect
                      value={it.category_id != null ? it.category_id : ""}
                      options={catOptions}
                      onChange={(v) => updateLineItem(it.id, v)}
                    />
                  : <span className="tiny muted">—</span>}
              </td>
              <td className="right amt">${it.amount.toFixed(2)}</td>
            </tr>
          ))}
          {r.charges.length === 0 && r.items.length === 0 && (
            <tr><td colSpan={4} className="muted small" style={{ textAlign: "center", padding: 16 }}>No line items.</td></tr>
          )}
          {r.charges.length > 0 && (
            <tr className="zebra">
              <td colSpan={3} className="tiny" style={{ color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: ".06em", fontWeight: 500 }}>Charges &amp; fees</td>
              <td></td>
            </tr>
          )}
          {r.charges.map((c, i) => (
            <tr key={"c"+i}>
              <td><Pill kind="muted">{c.amount < 0 ? "discount" : "tax/fee"}</Pill></td>
              <td colSpan={2} style={{ color: "var(--ink-2)", fontStyle: "italic" }}>{c.name}</td>
              <td className={"right amt " + (c.amount < 0 ? "neg" : "")}>{c.amount < 0 ? "−" : ""}${Math.abs(c.amount).toFixed(2)}</td>
            </tr>
          ))}
        </tbody>
        <tfoot>
          <tr>
            <td colSpan={3} style={{ textAlign: "right", color: "var(--ink-3)" }}>Items subtotal</td>
            <td className="right amt">${r.itemsSubtotal.toFixed(2)}</td>
          </tr>
          <tr>
            <td colSpan={3} style={{ textAlign: "right", color: "var(--ink-3)" }}>Charges subtotal</td>
            <td className="right amt">${r.chargesSubtotal.toFixed(2)}</td>
          </tr>
          <tr style={{ background: "var(--paper)" }}>
            <td colSpan={3} style={{ textAlign: "right", fontWeight: 600 }}>Stated total</td>
            <td className="right amt" style={{ fontWeight: 600 }}>${r.total.toFixed(2)}</td>
          </tr>
          {!r.sumMatches && (
            <tr>
              <td colSpan={3} style={{ textAlign: "right", color: "var(--negative)", fontStyle: "italic" }}>
                Computed (items + charges)
              </td>
              <td className="right amt neg">${r.computedTotal.toFixed(2)}</td>
            </tr>
          )}
        </tfoot>
      </table>
    </div>
  );
}

Object.assign(window, { TransactionDetailScreen, ReceiptDetailScreen, ReceiptLineItems });
