// diagnostics.jsx — unpaired view + Schedule C summary + Receivables editor

const { useState: gState, useMemo } = React;

// ────────────────────────────────────────────────────────────────────────────
// Diagnostics: two columns side-by-side
// ────────────────────────────────────────────────────────────────────────────
function DiagnosticsScreen({ db, setDb, navTo, push }) {
  const usedRids = useMemo(
    () => new Set(db.transactions.filter(t => t.receiptId).map(t => t.receiptId)),
    [db.transactions]
  );
  const unpairedReceipts = useMemo(
    () => db.receipts.filter(r => !usedRids.has(r.id)).sort((a, b) => a.date < b.date ? 1 : -1),
    [db.receipts, usedRids]
  );
  const unpairedTxns = useMemo(
    () => db.transactions.filter(t => !t.receiptId && t.amount < 0).sort((a, b) => a.date < b.date ? 1 : -1),
    [db.transactions]
  );
  const reviewTxns = useMemo(
    () => db.transactions.filter(t => t.needsReview),
    [db.transactions]
  );

  const [selR, setSelR] = gState(null);
  const [selT, setSelT] = gState(null);

  // Phase 4 wiring: pair a receipt to a transaction. The backend transaction
  // patch now accepts receipt_id (Decision 10.1); pairing also clears
  // needs_review server-side, which matches the optimistic patch here.
  // Optimistic update, then API call, reconcile with the server row, roll
  // back + error-toast on failure.
  const pair = (txnId, receiptId) => {
    const prevRow = db.transactions.find(t => t.id === txnId);
    setDb(prev => ({
      ...prev,
      transactions: prev.transactions.map(t => t.id === txnId ? { ...t, receiptId, needsReview: false } : t),
    }));
    setSelR(null); setSelT(null);
    ViewerAPI.patchTransaction(txnId, { receipt_id: receiptId })
      .then(resp => {
        const server = resp && resp.transaction;
        if (server) {
          setDb(prev => ({
            ...prev,
            transactions: prev.transactions.map(t => t.id === txnId ? { ...t, ...server } : t),
          }));
        }
        push("Paired");
      })
      .catch(err => {
        setDb(prev => ({
          ...prev,
          transactions: prev.transactions.map(t => t.id === txnId ? { ...t, ...prevRow } : t),
        }));
        push("Couldn't pair: " + err.message);
      });
  };

  // Phase 4 wiring: clear a review flag. Optimistic, then API call, rollback
  // on failure.
  const clearReview = (txnId) => {
    const prevRow = db.transactions.find(t => t.id === txnId);
    setDb(prev => ({
      ...prev,
      transactions: prev.transactions.map(x => x.id === txnId ? { ...x, needsReview: false } : x),
    }));
    ViewerAPI.patchTransaction(txnId, { needs_review: false })
      .then(resp => {
        const server = resp && resp.transaction;
        if (server) {
          setDb(prev => ({
            ...prev,
            transactions: prev.transactions.map(x => x.id === txnId ? { ...x, ...server } : x),
          }));
        }
        push("Cleared review flag");
      })
      .catch(err => {
        setDb(prev => ({
          ...prev,
          transactions: prev.transactions.map(x => x.id === txnId ? { ...x, ...prevRow } : x),
        }));
        push("Couldn't save: " + err.message);
      });
  };

  // Candidate match score between a receipt and a transaction
  const score = (r, t) => {
    const dT = Math.abs(r.total - Math.abs(t.amount));
    const dDays = Math.abs((parseDate(r.date) - parseDate(t.date)) / (1000*60*60*24));
    return dT + dDays * 4;
  };

  return (
    <div className="stack">
      <div className="grid-3">
        <KPICard label="Unpaired receipts" value={String(unpairedReceipts.length)} sub="Have a receipt; missing bank entry" />
        <KPICard label="Unpaired transactions" value={String(unpairedTxns.length)} sub="Bank debits with no receipt" />
        <KPICard label="Flagged for review" value={String(reviewTxns.length)} sub="Owner marked: needs a look" />
      </div>

      <div className="diag-grid">
        {/* Left: receipts with no matching transaction */}
        <Card title="Receipts without a matching transaction"
              meta={`${unpairedReceipts.length} items`}
              action={<span className="tiny muted">Phone snaps & emailed PDFs not yet posted on the bank side.</span>}
              flush>
          <div className="diag-list">
            {unpairedReceipts.length === 0 && <div className="muted small" style={{ padding: 20, textAlign: "center" }}>Nothing unpaired here. 🎉</div>}
            {unpairedReceipts.map(r => {
              const isSel = selR && selR.id === r.id;
              // Suggested match in the unpaired transactions column
              const suggestion = unpairedTxns.slice()
                .sort((a, b) => score(r, a) - score(r, b))[0];
              const suggestionGood = suggestion && score(r, suggestion) < 6;
              return (
                <div key={r.id}
                     className={"diag-row" + (isSel ? " selected" : "")}
                     onClick={() => setSelR(isSel ? null : r)}>
                  <div className="d-main">
                    <div className="row" style={{ gap: 6 }}>
                      <span className="name">{r.vendorShort}</span>
                      <Pill kind="muted" icon={r.arrival === "phone-snap" ? "phone" : "mail"}>
                        {r.arrival === "phone-snap" ? "snap" : "PDF"}
                      </Pill>
                      {!r.sumMatches && <Pill kind="neg">Δ</Pill>}
                    </div>
                    <div className="sub">{fmtDate(r.date)} · {r.id}{r.note ? " · " + r.note : ""}</div>
                  </div>
                  <div className="d-amt">${r.total.toFixed(2)}</div>

                  {isSel && (
                    <div style={{ gridColumn: "1 / -1", padding: "8px 0 4px", borderTop: "1px dashed var(--border)", marginTop: 4 }}>
                      {suggestion ? (
                        <div className="row between" style={{ gap: 12 }}>
                          <div>
                            <div className="tiny muted" style={{ marginBottom: 2 }}>
                              Best candidate from unpaired transactions {suggestionGood && <Pill kind="paired">good match</Pill>}
                            </div>
                            <div style={{ fontSize: 13 }}>{suggestion.description}</div>
                            <div className="tiny muted">{fmtDate(suggestion.date)} · {fmt(suggestion.amount)}</div>
                          </div>
                          <div className="row" style={{ gap: 6 }}>
                            <button className="btn sm" onClick={(e) => { e.stopPropagation(); navTo({ screen: "receipt", id: r.id }); }}>Open receipt</button>
                            <button className="btn sm primary" onClick={(e) => { e.stopPropagation(); pair(suggestion.id, r.id); }}>
                              Pair these →
                            </button>
                          </div>
                        </div>
                      ) : (
                        <div className="row between">
                          <div className="muted small">No good candidate among unpaired transactions.</div>
                          <button className="btn sm" onClick={(e) => { e.stopPropagation(); navTo({ screen: "receipt", id: r.id }); }}>Open receipt</button>
                        </div>
                      )}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </Card>

        {/* Right: transactions with no receipt */}
        <Card title="Transactions without a receipt"
              meta={`${unpairedTxns.length} items`}
              action={<span className="tiny muted">Bank debits — track down or mark not-applicable.</span>}
              flush>
          <div className="diag-list">
            {unpairedTxns.length === 0 && <div className="muted small" style={{ padding: 20, textAlign: "center" }}>All caught up.</div>}
            {unpairedTxns.map(t => {
              const isSel = selT && selT.id === t.id;
              const suggestion = unpairedReceipts.slice()
                .sort((a, b) => score(a, t) - score(b, t))[0];
              const suggestionGood = suggestion && score(suggestion, t) < 6;
              return (
                <div key={t.id}
                     className={"diag-row" + (isSel ? " selected" : "")}
                     onClick={() => setSelT(isSel ? null : t)}>
                  <div className="d-main">
                    <div className="row" style={{ gap: 6 }}>
                      <span className="name">{t.description}</span>
                      {t.needsReview && <Pill kind="review">review</Pill>}
                    </div>
                    <div className="sub">{fmtDate(t.date)} · {db.cat(t.category).label}{t.note ? " · " + t.note : ""}</div>
                  </div>
                  <div className="d-amt amt neg">{fmt(t.amount)}</div>

                  {isSel && (
                    <div style={{ gridColumn: "1 / -1", padding: "8px 0 4px", borderTop: "1px dashed var(--border)", marginTop: 4 }}>
                      {suggestion ? (
                        <div className="row between" style={{ gap: 12 }}>
                          <div>
                            <div className="tiny muted" style={{ marginBottom: 2 }}>
                              Best candidate from unpaired receipts {suggestionGood && <Pill kind="paired">good match</Pill>}
                            </div>
                            <div style={{ fontSize: 13 }}>{suggestion.vendorShort}</div>
                            <div className="tiny muted">{fmtDate(suggestion.date)} · ${suggestion.total.toFixed(2)}</div>
                          </div>
                          <div className="row" style={{ gap: 6 }}>
                            <button className="btn sm" onClick={(e) => { e.stopPropagation(); navTo({ screen: "txn", id: t.id }); }}>Open transaction</button>
                            <button className="btn sm primary" onClick={(e) => { e.stopPropagation(); pair(t.id, suggestion.id); }}>
                              Pair these →
                            </button>
                          </div>
                        </div>
                      ) : (
                        <div className="row between">
                          <div className="muted small">No good candidate among unpaired receipts.</div>
                          <button className="btn sm" onClick={(e) => { e.stopPropagation(); navTo({ screen: "txn", id: t.id }); }}>Open transaction</button>
                        </div>
                      )}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </Card>
      </div>

      {/* Needs-review */}
      <Card title="Flagged for review" meta={`${reviewTxns.length} transactions` } flush>
        <table className="t">
          <thead><tr>
            <th>Date</th><th>Description</th><th>Category</th><th className="right">Amount</th><th>Note</th><th></th>
          </tr></thead>
          <tbody>
            {reviewTxns.length === 0 && <tr><td colSpan={6} className="muted small" style={{ padding: 20, textAlign: "center" }}>Nothing flagged.</td></tr>}
            {reviewTxns.map(t => (
              <tr key={t.id} onClick={() => navTo({ screen: "txn", id: t.id })}>
                <td className="mono">{fmtDate(t.date)}</td>
                <td>{t.description}</td>
                <td>{db.cat(t.category).label}</td>
                <td className={"right amt " + (t.amount < 0 ? "neg" : "pos")}>{fmt(t.amount)}</td>
                <td className="tiny muted">{t.note || "—"}</td>
                <td className="right">
                  <button className="btn sm ghost" onClick={(e) => {
                    e.stopPropagation();
                    clearReview(t.id);
                  }}>
                    <Icon name="check" size={12} /> Clear
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </Card>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────────────
// Schedule C summary
// ────────────────────────────────────────────────────────────────────────────
function ScheduleCScreen({ db, navTo }) {
  const months = useMemo(() => Array.from(new Set(db.transactions.map(t => monthOf(t.date)))).sort().reverse(), [db.transactions]);
  const [scope, setScope] = gState("all"); // all | YYYY-MM

  const filteredTxns = useMemo(() => {
    if (scope === "all") return db.transactions;
    return db.transactions.filter(t => monthOf(t.date) === scope);
  }, [db.transactions, scope]);

  // Roll up by category
  const rollup = useMemo(() => {
    const map = {};
    db.categories.forEach(c => map[c.id] = { ...c, total: 0, count: 0 });
    filteredTxns.forEach(t => {
      if (t.category === "transfer" || t.category === "income") return;
      const amt = Math.abs(t.amount);
      if (t.amount < 0) {
        map[t.category].total += amt;
        map[t.category].count += 1;
      }
    });
    return Object.values(map).filter(c => c.count > 0 && c.id !== "income" && c.id !== "transfer").sort((a, b) => b.total - a.total);
  }, [filteredTxns, db.categories]);

  const totalExp = rollup.reduce((s, c) => s + c.total, 0);
  const totalInc = filteredTxns.filter(t => t.amount > 0 && t.category === "income").reduce((s, t) => s + t.amount, 0);
  const net = totalInc - totalExp;
  const maxCat = Math.max(...rollup.map(c => c.total), 1);

  return (
    <div className="stack">
      <div className="row between">
        <div className="row" style={{ gap: 10 }}>
          <span className="label">Period</span>
          <select className="select" value={scope} onChange={e => setScope(e.target.value)}>
            <option value="all">All months in view (last 3 mo)</option>
            {months.map(m => <option key={m} value={m}>{monthLabel(m)}</option>)}
          </select>
        </div>
        <div className="row" style={{ gap: 8 }}>
          <button className="btn sm"><Icon name="download" size={12} /> Export CSV</button>
          <button className="btn sm"><Icon name="copy" size={12} /> Copy 1040-SE figures</button>
        </div>
      </div>

      <div className="grid-3">
        <KPICard label="Gross receipts (line 1)" value={fmt(totalInc, false)} sub={`${filteredTxns.filter(t=>t.category==="income").length} deposits`} />
        <KPICard label="Total expenses" value={"−" + fmt(totalExp, false)} sub={`${rollup.length} categories with activity`} />
        <KPICard label="Net profit (est.)" value={fmtSigned(net, false)} sub={net < 0 ? "Net loss" : "Before depreciation & SE tax"} accent />
      </div>

      <Card title="Expense breakdown by Schedule C category" meta={scope === "all" ? "Trailing 3 months" : monthLabel(scope)} flush>
        <table className="t">
          <thead>
            <tr>
              <th>Category</th>
              <th>Schedule C line</th>
              <th className="right">Count</th>
              <th className="right">Total</th>
              <th>% of expenses</th>
            </tr>
          </thead>
          <tbody>
            {rollup.map(c => (
              <tr key={c.id} onClick={() => navTo({ screen: "transactions", filterCat: c.id, filterMonth: scope })}>
                <td>
                  <div style={{ fontWeight: 500 }}>{c.label}</div>
                </td>
                <td className="muted small">{c.schedC}</td>
                <td className="right mono">{c.count}</td>
                <td className="right amt">${c.total.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
                <td style={{ minWidth: 180 }}>
                  <div className="row" style={{ gap: 10, alignItems: "center" }}>
                    <div className="sched-cat-bar" style={{ flex: 1 }}>
                      <div style={{ width: ((c.total / maxCat) * 100) + "%" }} />
                    </div>
                    <span className="mono tiny muted" style={{ width: 36, textAlign: "right" }}>
                      {((c.total / totalExp) * 100).toFixed(0)}%
                    </span>
                  </div>
                </td>
              </tr>
            ))}
            {rollup.length === 0 && (
              <tr><td colSpan={5} className="muted" style={{ textAlign: "center", padding: 28 }}>No expenses in this period.</td></tr>
            )}
          </tbody>
          <tfoot>
            <tr style={{ background: "var(--paper)" }}>
              <td colSpan={2} style={{ fontWeight: 600 }}>Total deductible expenses</td>
              <td className="right mono">{rollup.reduce((s, c) => s + c.count, 0)}</td>
              <td className="right amt" style={{ fontWeight: 600 }}>${totalExp.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
              <td></td>
            </tr>
          </tfoot>
        </table>
      </Card>

      <div className="tiny muted" style={{ padding: "0 4px" }}>
        Click any category row to jump to the matching transactions. Figures are draft; verify before filing.
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────────────
// Receivables editor (small companion screen)
// ────────────────────────────────────────────────────────────────────────────
function ReceivablesScreen({ db, setDb, push }) {
  // Phase 4 wiring: shift a receivable's expected pay date. Optimistic patch +
  // local re-bucket (harmless on the degenerate Phase 4 forecast), then call
  // ViewerAPI.patchReceivable; reconcile with the server row, roll back on
  // failure. Inert in practice until Phase 5 populates `receivables`.
  const updateRec = (id, expected) => {
    const prevRow = db.receivables.find(r => r.id === id);
    setDb(prev => {
      const next = {
        ...prev,
        receivables: prev.receivables.map(r => r.id === id ? { ...r, expected } : r),
      };
      return rebucketForecast(next);
    });
    ViewerAPI.patchReceivable(id, expected)
      .then(resp => {
        const server = resp && resp.receivable;
        if (!server) return;
        setDb(prev => {
          const next = {
            ...prev,
            receivables: prev.receivables.map(r => r.id === id ? { ...r, ...server } : r),
          };
          return rebucketForecast(next);
        });
      })
      .catch(err => {
        setDb(prev => {
          const next = {
            ...prev,
            receivables: prev.receivables.map(r => r.id === id ? { ...r, ...prevRow } : r),
          };
          return rebucketForecast(next);
        });
        push("Couldn't save date: " + err.message);
      });
  };
  const total = db.receivables.reduce((s, r) => s + r.amount, 0);
  return (
    <div className="stack">
      <div className="grid-3">
        <KPICard label="Open receivables" value={String(db.receivables.length)} sub="Jobber + Wix" />
        <KPICard label="Total outstanding" value={fmt(total, false)} sub="Across all customers" />
        <KPICard label="Used in forecast" value={"All " + db.receivables.length} sub="Allocated to a week by expected pay date" accent />
      </div>

      <Card title="Open invoices" meta="Adjust expected pay date — forecast recalculates immediately" flush>
        <table className="t">
          <thead>
            <tr>
              <th>Invoice</th>
              <th>Customer</th>
              <th>Source</th>
              <th>Invoiced</th>
              <th>Expected pay date</th>
              <th className="right">Amount</th>
              <th>Note</th>
            </tr>
          </thead>
          <tbody>
            {db.receivables.length === 0 && (
              <tr><td colSpan={7} className="muted small" style={{ padding: 24, textAlign: "center" }}>No open receivables. (Populated in Phase 5.)</td></tr>
            )}
            {db.receivables.map(r => (
              <tr key={r.id}>
                <td className="mono">{r.id}</td>
                <td><strong>{r.customer}</strong></td>
                <td><Pill kind="info">{r.source}</Pill></td>
                <td className="mono">{fmtDate(r.invoiceDate)}</td>
                <td>
                  <input
                    className="input inline-edit"
                    type="date"
                    value={r.expected}
                    onChange={e => { updateRec(r.id, e.target.value); push("Updated expected pay date"); }}
                    style={{ width: 150 }}
                  />
                </td>
                <td className="right amt pos">+{fmt(r.amount, false)}</td>
                <td className="tiny muted">{r.note}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </Card>
    </div>
  );
}

// Recompute forecast from receivables / recurring / current starting balance + threshold.
function rebucketForecast(db) {
  const f = db.forecast;
  const weeks = f.weeks;
  const inflowsByWeek = weeks.map(() => ({ jobber: 0, wix: 0, items: [] }));
  db.receivables.forEach(r => {
    const exp = parseDate(r.expected);
    for (let i = 0; i < weeks.length; i++) {
      const ws = parseDate(weeks[i].start);
      const we = parseDate(weeks[i].end);
      if (exp >= ws && exp <= we) {
        if (r.source === "wix") inflowsByWeek[i].wix += r.amount;
        else inflowsByWeek[i].jobber += r.amount;
        inflowsByWeek[i].items.push(r);
      }
    }
  });
  let bal = f.startingBalance;
  const newWeeks = weeks.map((w, i) => {
    const inJ = inflowsByWeek[i].jobber;
    const inW = inflowsByWeek[i].wix;
    const inflow = inJ + inW;
    const outflow = w.outRecurring + w.outPayrollEst + w.outSupplierEst;
    const net = inflow - outflow;
    const starting = bal;
    const ending = starting + net;
    bal = ending;
    return {
      ...w,
      inflowJobber: inJ,
      inflowWix: inW,
      inflow,
      outflow,
      net,
      starting,
      ending,
      receivablesHits: inflowsByWeek[i].items,
      below: ending < f.lowCashThreshold,
      negative: ending < 0,
    };
  });
  return { ...db, forecast: { ...f, weeks: newWeeks } };
}

Object.assign(window, { DiagnosticsScreen, ScheduleCScreen, ReceivablesScreen, rebucketForecast });
