// ============================================================
// pages/revenue.jsx — Dashboard → Revenue (Phase 2).
// Summary bar chart (paid vs upcoming, Revenue | Profit,
// Monthly | Yearly), the summary card, and the Earnings
// comparison line chart with the "On pace to earn" table.
// Math: lib/revenue.js + lib/pacing.js. Drawing: charts.js.
// ============================================================
const MONTHS_L = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const PLATFORMS = [["all", "All platforms"], ["airbnb", "Airbnb"], ["vrbo", "VRBO"], ["booking", "Booking.com"], ["direct", "Direct"], ["other", "Other"]];
const LIVE_DATE = "2026-05-15";

function ymOf(today) { return { y: +today.slice(0, 4), m: +today.slice(5, 7) - 1 }; }

// Chart columns are buttons: click/tap, Enter or Space selects; Left/Right
// arrows move the selection and keep focus on the chart.
function chartKeys(e, sel, count, onSelect) {
  const t = e.target.closest && e.target.closest(".hit");
  if (!t) return;
  let i = null;
  if (e.key === "Enter" || e.key === " ") i = +t.dataset.i;
  else if (e.key === "ArrowLeft") i = Math.max(0, sel - 1);
  else if (e.key === "ArrowRight") i = Math.min(count - 1, sel + 1);
  if (i === null) return;
  e.preventDefault();
  // the chart redraws after onSelect; useRefocus puts focus on the new column
  e.currentTarget.dataset.refocus = "1";
  onSelect(i);
}

// after a keyboard selection redraws the chart, focus the selected column again
function useRefocus(ref, sel) {
  React.useEffect(() => {
    const box = ref.current;
    if (!box) return;
    const holder = box.dataset.refocus ? box : box.querySelector("[data-refocus]");
    if (!holder) return;
    delete holder.dataset.refocus;
    const next = holder.querySelector('.hit[data-i="' + sel + '"]');
    if (next) next.focus();
  });
}

// ---- the bar chart box: measures its width, hover tooltip, tap = select ----
function BarChartBox({ cols, sel, onSelect, onPrev, onNext, canPrev, canNext, label, tipTitle }) {
  const box = React.useRef(null);
  const W = window.useWidth(box);
  const phone = window.usePhone();
  const [hover, setHover] = React.useState(null);
  useRefocus(box, sel);
  const chart = window.AreteCharts.bars({ cols, sel, W, phone, label, tipFor: hover });
  const h = hover != null ? cols[hover] : null;
  const pick = (e) => { const t = e.target.closest(".hit"); if (t) onSelect(+t.dataset.i); };
  return (
    <>
      <div className="chartbox" ref={box}
        onMouseMove={(e) => { const t = e.target.closest(".hit"); setHover(t ? +t.dataset.i : null); }}
        onMouseLeave={() => setHover(null)}>
        <button className="arrow l" onClick={onPrev} disabled={!canPrev} aria-label="Previous"><Icon name="left" size={18} /></button>
        <div onClick={pick} onKeyDown={(e) => chartKeys(e, sel, cols.length, onSelect)} dangerouslySetInnerHTML={{ __html: chart.svg }} />
        {h && !phone && (h.p + h.u) !== 0 && (
          <div className="tip" style={{ left: chart.tip.left + "%", top: chart.tip.top + "%" }} aria-hidden="true">
            <b>{tipTitle(hover)}</b>
            <div className="r"><span className="dot paid" /><span>Paid</span><span className="num">{money(h.p, 2)}</span></div>
            <div className="r"><span className="dot up" /><span>Upcoming</span><span className="num">{money(h.u, 2)}</span></div>
            <hr />
            <div className="r"><span>Total (USD)</span><span className="num">{money(h.p + h.u, 2)}</span></div>
          </div>
        )}
        <button className="arrow r" onClick={onNext} disabled={!canNext} aria-label="Next"><Icon name="right" size={18} /></button>
      </div>
      <div className="mobile-arrows">
        <button className="arrow" onClick={onPrev} disabled={!canPrev} aria-label="Previous"><Icon name="left" size={18} /></button>
        <span className="num" style={{ fontWeight: 600 }}>{tipTitle(sel)}</span>
        <button className="arrow" onClick={onNext} disabled={!canNext} aria-label="Next"><Icon name="right" size={18} /></button>
      </div>
    </>
  );
}

function ColumnsTable({ cols, title }) {
  return (
    <div className="tcard scrollx" style={{ marginTop: 20 }}>
      <table className="tbl">
        <caption className="sr">{title}</caption>
        <thead><tr><th>{cols.length === 12 ? "Month" : "Year"}</th><th className="r">Paid</th><th className="r">Upcoming</th><th className="r">Total</th></tr></thead>
        <tbody>{cols.map((c) => (
          <tr key={c.l}><td>{c.full || c.l}</td><td className="r num">{money(c.p)}</td><td className="r num">{money(c.u)}</td><td className="r num">{money(c.p + c.u)}</td></tr>
        ))}</tbody>
      </table>
    </div>
  );
}

// bookings (or expense categories) behind the selected bar
function Breakdown({ mode, items }) {
  if (!items.length) return <div className="pace"><p className="sub" style={{ margin: "0 0 20px" }}>Nothing in this period.</p></div>;
  return (
    <div className="pace">
      <div className="scrollx">
        <table className="tbl">
          <thead><tr><th>{mode === "profit" ? "Category" : "Stay"}</th><th>{mode === "profit" ? "Source" : "Status"}</th><th className="r">{mode === "profit" ? "Spent" : "Payout"}</th></tr></thead>
          <tbody>{items.map((it) => (
            <tr key={it.key}><td>{it.name}<span className="th2">{it.sub}</span></td><td>{it.tag}</td><td className="r num">{money(it.amount, 2)}</td></tr>
          ))}</tbody>
        </table>
      </div>
    </div>
  );
}

function RevenuePage() {
  const today = window.todayNY();
  const now = ymOf(today);
  const t = window.useTables(["reservations", "transactions", "recurring_costs", "curves"]);
  const [view, setView] = React.useState("monthly");
  const [mode, setMode] = React.useState("revenue");
  const [year, setYear] = React.useState(now.y);
  const [month, setMonth] = React.useState(now.m);
  const [yearPick, setYearPick] = React.useState(now.y);   // selected year in Yearly view
  const [asTable, setAsTable] = React.useState(false);
  const [platform, setPlatform] = React.useState("all");
  const [breakdown, setBreakdown] = React.useState(false);

  if (t.status === "loading") return <Loading />;
  if (t.status === "error") return <LoadError error={t.error} onRetry={t.reload} />;

  const allRes = t.data.reservations || [];
  const Rv = window.AreteRevenue;
  const res = platform === "all" ? allRes : allRes.filter((r) => (r.platform || "").toLowerCase() === platform);
  const revenue = Rv.monthly(res, { today });
  // every year from the first to the last that has money in it (no gaps), filled
  // in BEFORE the profit math so a year with costs but no bookings shows its loss
  const known = Object.keys(revenue).map(Number).concat(now.y,
    (t.data.transactions || []).filter((x) => x.date && x.date >= LIVE_DATE).map((x) => +x.date.slice(0, 4)));
  const years = [];
  for (let y = Math.min(...known); y <= Math.max(...known); y++) {
    years.push(y);
    if (!revenue[y]) revenue[y] = Array.from({ length: 12 }, () => ({ paid: 0, upcoming: 0 }));
  }
  const byYear = mode === "profit"
    ? Rv.profitMonthly(revenue, t.data.transactions || [], t.data.recurring_costs || [], { today, liveDate: LIVE_DATE })
    : revenue;
  // a filter can remove the year being shown: fall back to this year
  const yr = years.includes(year) ? year : (years.includes(now.y) ? now.y : years[years.length - 1]);
  const yIdx = years.indexOf(yr);
  const zero = () => Array.from({ length: 12 }, () => ({ paid: 0, upcoming: 0 }));

  let cols, sel, label, title;
  if (view === "monthly") {
    const row = byYear[yr] || zero();
    cols = row.map((v, i) => ({ l: window.AreteCharts.MONTHS[i], full: MONTHS_L[i] + " " + yr, p: v.paid, u: v.upcoming }));
    sel = month;
    title = (i) => MONTHS_L[i] + " " + yr;
  } else {
    cols = years.map((y) => {
      const s = (byYear[y] || zero()).reduce((a, v) => ({ p: a.p + v.paid, u: a.u + v.upcoming }), { p: 0, u: 0 });
      return { l: String(y), p: s.p, u: s.u };
    });
    sel = years.indexOf(yearPick);
    if (sel < 0) sel = years.indexOf(now.y);
    title = (i) => String(years[i]);
  }
  cols.forEach((k, i) => { k.aria = `${title(i)}: ${money(k.p)} paid, ${money(k.u)} upcoming`; });
  const c = cols[sel];
  label = `${mode === "profit" ? "Profit" : "Revenue"} by ${view === "monthly" ? "month for " + yr : "year"}. ${title(sel)}: ${money(c.p)} paid, ${money(c.u)} upcoming.`;

  const step = (d) => {
    if (view === "yearly") { setYearPick(years[Math.max(0, Math.min(cols.length - 1, sel + d))]); return; }
    let m = month + d, y = yr;
    if (m < 0) { m = 11; y -= 1; } else if (m > 11) { m = 0; y += 1; }
    if (!years.includes(y)) return;
    setMonth(m); setYear(y);
  };
  const canPrev = view === "monthly" ? (month > 0 || yIdx > 0) : sel > 0;
  const canNext = view === "monthly" ? (month < 11 || yIdx < years.length - 1) : sel < cols.length - 1;

  // what sits behind the selected bar — the same rules as lib/revenue.js
  const pYear = view === "monthly" ? yr : years[sel];
  const inPeriod = (d) => d && +d.slice(0, 4) === pYear && (view === "yearly" || +d.slice(5, 7) - 1 === month);
  const curYM = today.slice(0, 7);
  let items;
  if (mode === "profit") {
    const spent = (t.data.transactions || []).filter((x) => (x.class || "").toLowerCase() === "expense"
      && x.date >= LIVE_DATE && x.date.slice(0, 7) <= curYM && inPeriod(x.date))
      .reduce((a, x) => { const k = x.category || "Uncategorized"; a[k] = (a[k] || 0) - Number(x.amount); return a; }, {});
    items = Object.entries(spent).sort((a, b) => b[1] - a[1])
      .map(([k, v]) => ({ key: "x:" + k, name: k, sub: "from the ledger", tag: "Spent", amount: v }));
    // months still ahead use the recurring-cost budget
    const ahead = (view === "monthly" ? [month] : Array.from({ length: 12 }, (_, i) => i))
      .filter((i) => pYear + "-" + String(i + 1).padStart(2, "0") > curYM).length;
    if (ahead) {
      (t.data.recurring_costs || []).filter((r) => (r.status || "") !== "cancelled" && Number(r.monthly_budget) > 0)
        .forEach((r) => items.push({ key: "b:" + r.id, name: r.vendor, sub: (r.what || "") + (ahead > 1 ? " · " + ahead + " months" : ""),
          tag: "Budget", amount: Number(r.monthly_budget) * ahead }));
    }
  } else {
    const counts = (r) => (r.status || "").toLowerCase() !== "cancelled" || (Number(r.payout) > 0 && r.checkin <= today);
    items = res.filter((r) => inPeriod(r.checkin) && counts(r))
      .sort((a, b) => (a.checkin < b.checkin ? -1 : 1))
      .map((r) => ({ key: r.id, name: r.guest_name || "Guest",
        sub: r.checkin + " → " + r.checkout + ((r.status || "").toLowerCase() === "cancelled" ? " · cancelled, payout kept" : ""),
        tag: (r.checkin <= today ? "Paid" : "Upcoming") + " · " + (r.platform || ""), amount: Number(r.payout) || 0 }));
  }

  return (
    <>
      <section>
        <div className="secthead">
          <h2>Summary</h2>
          <button className="dd" onClick={() => setView(view === "monthly" ? "yearly" : "monthly")} aria-label="Switch monthly or yearly view">
            {view === "monthly" ? "Monthly view" : "Yearly view"} <Icon name="down" size={16} />
          </button>
          <button className="dd" onClick={() => setMode(mode === "revenue" ? "profit" : "revenue")} aria-label="Switch revenue or profit">
            {mode === "revenue" ? "Revenue" : "Profit"} <Icon name="down" size={16} />
          </button>
          <div className="right">
            <button className="iconbtn" aria-label="Show as table" title="Show as table" aria-pressed={asTable} onClick={() => setAsTable(!asTable)}><Icon name="grid" size={18} /></button>
            <Popover label="Filter by platform" icon="sliders" pressed={platform !== "all"}>
              {(close) => PLATFORMS.map(([k, l]) => (
                <label key={k}><input type="radio" name="platform" checked={platform === k} onChange={() => { setPlatform(k); close(); }} />{l}</label>
              ))}
            </Popover>
          </div>
        </div>
        {!allRes.length && mode === "revenue" && <p className="sub" style={{ marginTop: -8 }}>No bookings yet. They arrive with the hourly Hospitable sync and the Mac push, which are not turned on yet.</p>}
        {platform !== "all" && <p className="sub" style={{ marginTop: -8 }}>{PLATFORMS.find((p) => p[0] === platform)[1]} only</p>}
        <BarChartBox cols={cols} sel={sel} label={label} tipTitle={title}
          onSelect={(i) => (view === "monthly" ? setMonth(i) : setYearPick(years[i]))}
          onPrev={() => step(-1)} onNext={() => step(1)} canPrev={canPrev} canNext={canNext} />
        {asTable && <ColumnsTable cols={cols} title={label} />}
        <div className="card" style={{ marginTop: 36 }}>
          <div className="in">
            <h3>{title(sel)}{mode === "profit" ? " · profit" : ""}</h3>
            <div className="trio">
              <div><div className="fig num">{money(c.p, 2)}</div><div className="lab"><span className="dot paid" />Paid</div></div>
              <div><div className="fig num">{money(c.u, 2)}</div><div className="lab"><span className="dot up" />Upcoming</div></div>
              <div><div className="fig num">{money(c.p + c.u, 2)}</div><div className="lab">Total (USD)</div></div>
            </div>
          </div>
          <FootToggle open={breakdown} onClick={() => setBreakdown(!breakdown)}
            show={mode === "profit" ? "Expense breakdown" : "Booking breakdown"} hide={mode === "profit" ? "Hide expense breakdown" : "Hide booking breakdown"} />
          {breakdown && <Breakdown mode={mode} items={items} />}
        </div>
        {mode === "profit" && <p className="sub" style={{ marginTop: 16 }}>Profit = payout minus operating costs from the ledger. Months still ahead use the budgeted recurring costs.</p>}
      </section>
      <EarningsComparison t={t} today={today} reservations={allRes} />
    </>
  );
}

// ---- Earnings comparison: line chart, month card, On pace to earn ----
function LineChartBox({ series, sel, onSelect, label, monthLabels }) {
  const box = React.useRef(null);
  const W = window.useWidth(box);
  const phone = window.usePhone();
  const svg = window.AreteCharts.lines({ series, sel, W, phone, label, monthLabels });
  useRefocus(box, sel);
  return (
    <>
      <div className="chartbox" ref={box}
        onClick={(e) => { const t = e.target.closest(".hit"); if (t) onSelect(+t.dataset.i); }}
        onKeyDown={(e) => chartKeys(e, sel, 12, onSelect)}
        dangerouslySetInnerHTML={{ __html: svg }} />
      <div className="mobile-arrows">
        <button className="arrow" onClick={() => onSelect(Math.max(0, sel - 1))} disabled={sel === 0} aria-label="Previous month"><Icon name="left" size={18} /></button>
        <span className="num" style={{ fontWeight: 600 }}>{MONTHS_L[sel]}</span>
        <button className="arrow" onClick={() => onSelect(Math.min(11, sel + 1))} disabled={sel === 11} aria-label="Next month"><Icon name="right" size={18} /></button>
      </div>
    </>
  );
}

const pct = (v) => (v == null ? "—" : Math.round(v) + "%");
const amt = (v) => (v == null ? "—" : money(v));

function EarningsComparison({ t, today, reservations }) {
  const now = ymOf(today);
  const [sel, setSel] = React.useState(now.m);
  const [showMonths, setShowMonths] = React.useState(false);
  const curves = t.data.curves || [];
  const calc = window.AretePacing.calculate(reservations, curves, { today, liveDate: LIVE_DATE });
  const byMonth = Array(12).fill(null);
  calc.months.calendar.forEach((m) => { byMonth[m.month - 1] = m; });
  const pick = (k) => byMonth.map((m) => (m == null || m[k] == null ? null : m[k]));
  const paceAll = calc.curves.market ? calc.curves.market.map((f) => 120000 * f) : Array(12).fill(null);
  const series = { us: pick("booked"), pace: paceAll, market: pick("marketCurve"), comps: pick("compsCurve") };
  const m = byMonth[sel];
  const monthLabels = MONTHS_L.map((n, i) => `${n} ${calc.year}: us ${amt(series.us[i])}, needed for $120k ${amt(paceAll[i])}`);
  const noCurves = !calc.curves.market && !calc.curves.comps;
  const rows = calc.table.rows;
  const [cal, first] = calc.table.columns;
  return (
    <section>
      <div className="secthead"><h2>Earnings comparison</h2><span className="dd" style={{ cursor: "default" }}>{calc.year} vs pace, market &amp; comps</span></div>
      {noCurves && <p className="sub" style={{ marginTop: -8 }}>The market and comps curves arrive with the first Mac push. Until then only our line shows.</p>}
      <LineChartBox series={series} sel={sel} onSelect={setSel} monthLabels={monthLabels}
        label={`Earnings comparison for ${calc.year}: us against the $120k pace, the market and the comps, month by month.`} />
      <div className="card" style={{ marginTop: 28 }}>
        <div className="in">
          <h3>{MONTHS_L[sel]} {calc.year}</h3>
          <div className="legend quad">
            <div className="it"><span className="fig num">{amt(m && m.booked)}</span><span className="lab"><span className="dot" style={{ background: "#E31E75" }} />Us</span></div>
            <div className="it"><span className="fig num">{amt(paceAll[sel])}</span><span className="lab"><svg width="18" height="6" aria-hidden="true"><line x1="0" x2="18" y1="3" y2="3" stroke="#6a6a6a" strokeWidth="1.5" strokeDasharray="4 3" /></svg>Needed for $120k</span></div>
            <div className="it"><span className="fig num">{amt(m && m.marketCurve)}</span><span className="lab"><span className="sq" style={{ background: "#8A1C6E" }} />Market</span></div>
            <div className="it"><span className="fig num">{amt(m && m.compsCurve)}</span><span className="lab"><svg width="18" height="6" aria-hidden="true"><line x1="1" x2="18" y1="3" y2="3" stroke="#9a9a9a" strokeWidth="2" strokeDasharray="1.5 3" strokeLinecap="round" /></svg>Comps</span></div>
          </div>
        </div>
        <div className="pace">
          <h3>On pace to earn</h3>
          <p className="sub" style={{ margin: "-16px 0 8px" }}>After OTA fees. Goal $120k a year, home run $140k. Open months are filled using each model's seasonal shape.</p>
          <div className="scrollx">
            <table className="tbl">
              <thead><tr>
                <th>Projection</th>
                <th className="r">{cal.label}<span className="th2">{fmtRange(cal.start, cal.end)}</span></th>
                <th className="r">{first.label}<span className="th2">{fmtRange(first.start, first.end, true)}</span></th>
                <th className="r">vs $120k goal</th><th className="r">vs $140k home run</th>
              </tr></thead>
              <tbody>{rows.map((r) => (
                <tr key={r.id} className={r.id === "bestEstimate" ? "us" : ""}>
                  <td>{r.label.replace(/ \(best estimate\)$/, "")}{r.id === "booked" && <span className="th2">no projection</span>}{r.id === "bestEstimate" && <span className="th2">best estimate</span>}</td>
                  <td className="r num">{amt(r[cal.id].amount)}</td>
                  <td className="r num">{amt(r[first.id].amount)}</td>
                  <td className="r num">{pct(r[first.id].goal.percent)}</td>
                  <td className="r num">{pct(r[first.id].homeRun.percent)}</td>
                </tr>
              ))}</tbody>
            </table>
          </div>
        </div>
        <FootToggle open={showMonths} onClick={() => setShowMonths(!showMonths)} show="Show monthly numbers" hide="Hide monthly numbers" />
        {showMonths && (
          <div className="pace" style={{ borderTop: "1px solid var(--line-strong)" }}>
            <div className="scrollx">
              <table className="tbl">
                <thead><tr><th>Month</th><th className="r">Us</th><th className="r">Needed for $120k</th><th className="r">Market</th><th className="r">Comps</th></tr></thead>
                <tbody>{MONTHS_L.map((name, i) => {
                  const x = byMonth[i], ym = calc.year + "-" + String(i + 1).padStart(2, "0");
                  const note = ym === today.slice(0, 7) ? "part booked" : ym > today.slice(0, 7) && x ? "booked so far" : "";
                  return (
                    <tr key={ym}>
                      <td>{name} {calc.year}{note && <span className="th2">{note}</span>}</td>
                      <td className="r num">{amt(series.us[i])}</td><td className="r num">{amt(paceAll[i])}</td>
                      <td className="r num">{amt(series.market[i])}</td><td className="r num">{amt(series.comps[i])}</td>
                    </tr>
                  );
                })}</tbody>
              </table>
            </div>
          </div>
        )}
      </div>
    </section>
  );
}

// "May 15 – Dec 31" or "May 15, 2026 – May 14, 2027"
function fmtRange(a, b, withYear) {
  const f = (d) => window.AreteCharts.MONTHS[+d.slice(5, 7) - 1] + " " + +d.slice(8, 10) + (withYear ? ", " + d.slice(0, 4) : "");
  return f(a) + " – " + f(b);
}

window.ARETE_PAGES = Object.assign(window.ARETE_PAGES || {}, { "dashboard/revenue": RevenuePage });
