// ===========================================================
// The Lead-In — "The Index": your collection as a daily portfolio.
//
// The reveal-panel entry point lives at the top of /collection (a button in the
// value bar). This is the panel it opens: a daily whole-collection value ticker
// + sparkline, the biggest movers over a rolling window, and sell signals.
//
// DATA — reads valuation_snapshots_daily (owner-only RLS, so a member only ever
// sees their own items' snapshots; nothing else is reachable client-side). That
// table is filled by capture_valuation_snapshots_daily() (06:00 UTC daily) off
// the est_value the reprice cron keeps fresh (bumped to every 15 min so the whole
// catalogue is repriced within a day). Market value only — purchase cost never
// enters a snapshot; the sell-signal comparison to purchase_price is done here on
// the items already in memory, never stored.
//
// THE HONEST SPLIT (same promise as ValueHistoryChart_v1):
//   - is_estimated = true  -> the pre-launch seed (carried from the weekly
//                             history). Drawn DASHED / muted.
//   - is_estimated = false -> a real daily print from the cron. Drawn SOLID in
//                             the accent. Daily movement + movers are computed
//                             from REAL prints only, so nothing reads as a market
//                             move that was really the seed or the reprice pointer.
//
// PORTFOLIO SERIES — per-item snapshots are sparse (an item is not re-priced
// every single day), so a naive per-day SUM would collapse on days with few
// rows. We instead carry each item's last-known value forward to every day in
// the window and sum ("as-of" valuation), which is how a portfolio time series is
// meant to be built. A plotted day is "live" only once real daily prints exist
// for it; before that the whole line is the dashed seed.
//
// Globals in: React, window.BBR_supabase. Exposes window.TheIndex.
// ===========================================================
const {
  useState: useTIState, useEffect: useTIEffect, useMemo: useTIMemo, useRef: useTIRef,
} = React;

const TI_DAY = 86400000;
const TI_WINDOW_DAYS = 120;   // matches the daily table's rolling retention
const TI_MOVER_DAYS = 7;      // rolling window for "biggest mover"
const TI_SELL_PCT = 25;       // flag records up >= this % vs what you paid

const TI_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function tiMoney(n) {
  if (n === null || n === undefined || !isFinite(Number(n))) return "—";
  const num = Number(n);
  const v = (Math.abs(num) < 100 && Math.round(num) !== num)
    ? num.toLocaleString("en-GB", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
    : Math.round(num).toLocaleString("en-GB");
  return "£" + v;
}
function tiSignedMoney(n) {
  const s = n > 0 ? "+" : n < 0 ? "−" : "";
  return s + tiMoney(Math.abs(n));
}
function tiDayStr(t) {
  const d = new Date(t);
  return d.getUTCDate() + " " + TI_MONTHS[d.getUTCMonth()];
}
// Truncate a timestamp to a UTC calendar-day integer (days since epoch).
function tiDayKey(t) { return Math.floor(t / TI_DAY); }

// ---- data fetch ------------------------------------------------------------
// Fetches the owner's daily snapshots (windowed, paginated — PostgREST caps a
// page at 1000 rows, and a large collection over 120 days easily exceeds that).
// Plain async so both the panel (via the hook below) AND the /collection value
// bar can share it and compute the SAME daily delta.
async function tiFetchDailySnaps(userId) {
  if (!userId || !window.BBR_supabase) return {};
  const sinceIso = new Date(Date.now() - TI_WINDOW_DAYS * TI_DAY).toISOString();
  const PAGE = 1000;
  const byItem = {};
  for (let from = 0; ; from += PAGE) {
    let resp;
    try {
      resp = await window.BBR_supabase
        .from("valuation_snapshots_daily")
        .select("collection_item_id,value,captured_at,is_estimated")
        .gte("captured_at", sinceIso)
        .order("captured_at", { ascending: true })
        .range(from, from + PAGE - 1);
    } catch (e) { break; }
    if (!resp || resp.error) break;
    const rows = resp.data || [];
    rows.forEach((r) => {
      const v = Number(r.value);
      if (!isFinite(v)) return;
      const id = r.collection_item_id;
      (byItem[id] || (byItem[id] = [])).push({
        v, t: new Date(r.captured_at).getTime(), est: r.is_estimated === true,
      });
    });
    if (rows.length < PAGE) break;
  }
  return byItem;
}

function useDailySnaps(userId) {
  const [state, setState] = useTIState({ loading: true, byItem: null });
  useTIEffect(() => {
    let active = true;
    setState({ loading: true, byItem: null });
    tiFetchDailySnaps(userId).then((byItem) => { if (active) setState({ loading: false, byItem }); });
    return () => { active = false; };
  }, [userId]);
  return state;
}

// ---- portfolio maths (carry-forward as-of valuation) -----------------------
function buildPortfolio(byItem) {
  const itemIds = Object.keys(byItem);
  if (!itemIds.length) return null;

  // First day a real daily print exists anywhere — the dashed→solid boundary.
  let firstRealDay = Infinity;
  itemIds.forEach((id) => byItem[id].forEach((s) => {
    if (!s.est) firstRealDay = Math.min(firstRealDay, tiDayKey(s.t));
  }));
  const hasReal = isFinite(firstRealDay);

  // Day axis: earliest snapshot day → today, one point per day.
  let minDay = Infinity, maxDay = tiDayKey(Date.now());
  itemIds.forEach((id) => byItem[id].forEach((s) => { minDay = Math.min(minDay, tiDayKey(s.t)); }));
  if (!isFinite(minDay)) return null;
  minDay = Math.max(minDay, maxDay - TI_WINDOW_DAYS);

  // Pre-sort each item's snapshots ascending for the carry-forward walk.
  itemIds.forEach((id) => byItem[id].sort((a, b) => a.t - b.t));

  const series = [];
  for (let day = minDay; day <= maxDay; day++) {
    let total = 0, held = 0;
    for (const id of itemIds) {
      const list = byItem[id];
      // latest snapshot on-or-before this day
      let val = null;
      for (let i = list.length - 1; i >= 0; i--) {
        if (tiDayKey(list[i].t) <= day) { val = list[i].v; break; }
      }
      if (val !== null) { total += val; held++; }
    }
    if (held > 0) series.push({ day, total, live: hasReal && day >= firstRealDay });
  }
  if (!series.length) return null;

  // Headline "today" figure + daily delta from REAL prints only.
  const cur = series[series.length - 1];
  const liveDays = series.filter((p) => p.live);
  let todayDelta = null;
  if (liveDays.length >= 2) {
    const a = liveDays[liveDays.length - 2], b = liveDays[liveDays.length - 1];
    const d = b.total - a.total;
    todayDelta = { d, pct: a.total ? (d / a.total) * 100 : null, dir: Math.abs(d) < 0.005 ? "flat" : (d > 0 ? "up" : "down") };
  }

  return { series, current: cur.total, todayDelta, liveDayCount: liveDays.length, firstRealDay, hasReal };
}

// Per-item movement over the rolling window, REAL prints only (needs >= 2).
function buildMovers(byItem, itemMeta) {
  const out = [];
  Object.keys(byItem).forEach((id) => {
    const reals = byItem[id].filter((s) => !s.est).sort((a, b) => a.t - b.t);
    if (reals.length < 2) return;
    const latest = reals[reals.length - 1];
    const target = latest.t - TI_MOVER_DAYS * TI_DAY;
    let base = reals[0], bd = Math.abs(reals[0].t - target);
    reals.forEach((s) => { const d = Math.abs(s.t - target); if (d < bd) { bd = d; base = s; } });
    const d = latest.v - base.v;
    if (Math.abs(d) < 0.005) return;
    const meta = itemMeta[id];
    out.push({
      id, delta: d, pct: base.v ? (d / base.v) * 100 : null, value: latest.v,
      title: meta ? meta.title : "A record", artist: meta ? meta.artist : "",
    });
  });
  out.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
  return out;
}

// Sell signals from the items already in memory (no snapshot needed): records up
// >= TI_SELL_PCT vs what you paid. Gifts and unpriced/unknown-cost rows excluded.
function buildSellSignals(items) {
  const out = [];
  (items || []).forEach((r) => {
    if (!r || r.is_gift === true) return;
    const cost = r.purchase_price;
    const val = typeof r.est_value === "number" ? r.est_value : null;
    if (cost === null || cost === undefined || val === null) return;
    const c = Number(cost);
    if (!isFinite(c) || c <= 0) return;
    const gain = val - c;
    const pct = (gain / c) * 100;
    if (pct < TI_SELL_PCT) return;
    out.push({ id: r.id, title: r.title, artist: r.artist, value: val, gain, pct });
  });
  out.sort((a, b) => b.pct - a.pct);
  return out;
}

// ---- sparkline -------------------------------------------------------------
function IndexSparkline({ series }) {
  const boxRef = useTIRef(null);
  const [width, setWidth] = useTIState(560);
  useTIEffect(() => {
    const el = boxRef.current;
    if (!el) return;
    const measure = () => setWidth(Math.max(240, Math.round(el.clientWidth)));
    measure();
    let ro = null;
    if (typeof ResizeObserver !== "undefined") { ro = new ResizeObserver(measure); ro.observe(el); }
    else { window.addEventListener("resize", measure); }
    return () => { if (ro) ro.disconnect(); else window.removeEventListener("resize", measure); };
  }, []);

  const H = 120, padL = 6, padR = 6, padT = 12, padB = 16;
  const view = useTIMemo(() => {
    if (!series || series.length < 2) return null;
    const innerW = Math.max(10, width - padL - padR), innerH = H - padT - padB;
    const vals = series.map((p) => p.total);
    let lo = Math.min.apply(null, vals), hi = Math.max.apply(null, vals);
    if (lo === hi) { const p = Math.max(1, Math.abs(lo) * 0.08); lo -= p; hi += p; }
    else { const p = (hi - lo) * 0.14; lo -= p; hi += p; }
    if (lo < 0) lo = 0;
    const span = (hi - lo) || 1;
    const d0 = series[0].day, d1 = series[series.length - 1].day, dspan = (d1 - d0) || 1;
    const X = (d) => padL + ((d - d0) / dspan) * innerW;
    const Y = (v) => padT + (1 - (v - lo) / span) * innerH;
    const pts = series.map((p) => ({ x: X(p.day), y: Y(p.total), live: p.live, p }));
    const segs = [];
    for (let i = 1; i < pts.length; i++) segs.push({ live: pts[i - 1].live && pts[i].live, a: pts[i - 1], b: pts[i] });
    return { pts, segs, innerH };
  }, [series, width]);

  return (
    <div className="ti-spark" ref={boxRef}>
      {view && (
        <svg className="ti-svg" width={width} height={H} viewBox={"0 0 " + width + " " + H}
          role="img" aria-label="Your collection value over time">
          {(() => {
            // Soft area fill under the line for the whole series.
            const base = padT + view.innerH;
            const d = "M" + view.pts.map((p) => p.x + " " + p.y).join(" L ")
              + " L " + view.pts[view.pts.length - 1].x + " " + base
              + " L " + view.pts[0].x + " " + base + " Z";
            return <path className="ti-area" d={d} />;
          })()}
          {view.segs.map((sg, i) => (
            <line key={i} className={sg.live ? "ti-line-live" : "ti-line-est"} x1={sg.a.x} y1={sg.a.y} x2={sg.b.x} y2={sg.b.y} />
          ))}
          {(() => {
            const last = view.pts[view.pts.length - 1];
            return <circle className={last.live ? "ti-dot-live" : "ti-dot-est"} cx={last.x} cy={last.y} r="3.5" />;
          })()}
        </svg>
      )}
      <div className="ti-spark-axis">
        <span>{series && series.length ? tiDayStr(series[0].day * TI_DAY) : ""}</span>
        <span>Today</span>
      </div>
    </div>
  );
}

// ---- the panel -------------------------------------------------------------
// props: { user, items, onClose }
function TheIndex({ user, items, onClose }) {
  const { loading, byItem } = useDailySnaps(user && user.id);

  // Scroll-lock the page behind the panel (iOS-safe: position:fixed + top offset;
  // iOS ignores body{overflow:hidden}). Restored on close.
  useTIEffect(() => {
    const y = window.pageYOffset || 0;
    const b = document.body.style;
    const prev = { position: b.position, top: b.top, width: b.width, overflow: b.overflow };
    b.position = "fixed"; b.top = -y + "px"; b.width = "100%"; b.overflow = "hidden";
    return () => {
      b.position = prev.position; b.top = prev.top; b.width = prev.width; b.overflow = prev.overflow;
      window.scrollTo(0, y);
    };
  }, []);

  const itemMeta = useTIMemo(() => {
    const m = {};
    (items || []).forEach((r) => { m[r.id] = { title: r.title, artist: r.artist }; });
    return m;
  }, [items]);

  const port = useTIMemo(() => (byItem ? buildPortfolio(byItem) : null), [byItem]);
  const movers = useTIMemo(() => (byItem ? buildMovers(byItem, itemMeta) : []), [byItem, itemMeta]);
  const sells = useTIMemo(() => buildSellSignals(items), [items]);

  const topMovers = movers.slice(0, 4);
  const topMover = movers[0] || null;

  return (
    <div className="ti-overlay" onClick={onClose}>
      <div className="ti-panel" onClick={(e) => e.stopPropagation()} role="dialog" aria-label="The Index — your daily collection report">
        <div className="ti-head">
          <div>
            <div className="ti-eyebrow">The Index</div>
            <div className="ti-sub">Your collection as a portfolio · refreshed daily</div>
          </div>
          <button type="button" className="ti-close" aria-label="Close" onClick={onClose}>✕</button>
        </div>

        {loading ? (
          <div className="ti-loading">Reading today’s valuations…</div>
        ) : !port ? (
          <div className="ti-empty">
            <div className="ti-empty-t">Your Index opens once your records are valued</div>
            <div className="ti-empty-s">Add or import records and their first daily valuation lands within a day.</div>
          </div>
        ) : (
          <div className="ti-body">
            {/* Hero: total + today's move */}
            <div className="ti-hero">
              <div className="ti-hero-total">{tiMoney(port.current)}</div>
              {port.todayDelta ? (
                <div className={"ti-hero-move " + port.todayDelta.dir}>
                  {port.todayDelta.dir !== "flat" && <span className="ti-arrow">{port.todayDelta.dir === "up" ? "▲" : "▼"}</span>}
                  {port.todayDelta.dir === "flat" ? "No change" : tiSignedMoney(port.todayDelta.d)}
                  {port.todayDelta.pct !== null && port.todayDelta.dir !== "flat" && (
                    <span className="ti-hero-pct"> ({port.todayDelta.dir === "up" ? "+" : "−"}{Math.abs(port.todayDelta.pct).toFixed(2)}%)</span>
                  )}
                  <small>today</small>
                </div>
              ) : (
                <div className="ti-hero-move building"><small>Live daily tracking has started — your first daily move shows tomorrow.</small></div>
              )}
            </div>

            <IndexSparkline series={port.series} />

            {/* Biggest mover(s) */}
            <div className="ti-section">
              <div className="ti-sec-title">Biggest mover</div>
              {topMover ? (
                <div className="ti-movers">
                  {topMovers.map((m) => (
                    <div className="ti-mover" key={m.id}>
                      <div className="ti-mover-rec">
                        <div className="ti-mover-title">{m.title}</div>
                        <div className="ti-mover-artist">{m.artist}</div>
                      </div>
                      <div className={"ti-mover-delta " + (m.delta > 0 ? "up" : "down")}>
                        <span className="ti-arrow">{m.delta > 0 ? "▲" : "▼"}</span>{tiMoney(Math.abs(m.delta))}
                        {m.pct !== null && <small>{m.delta > 0 ? "+" : "−"}{Math.abs(m.pct).toFixed(1)}%</small>}
                      </div>
                    </div>
                  ))}
                  <div className="ti-note">Movement over the last {TI_MOVER_DAYS} days, from live valuations.</div>
                </div>
              ) : (
                <div className="ti-quiet">Daily movers build over your first week of live tracking — check back soon.</div>
              )}
            </div>

            {/* Sell signals */}
            <div className="ti-section">
              <div className="ti-sec-title">Sell signals</div>
              {sells.length ? (
                <div className="ti-sells">
                  {sells.slice(0, 4).map((s) => (
                    <div className="ti-sell" key={s.id}>
                      <div className="ti-mover-rec">
                        <div className="ti-mover-title">{s.title}</div>
                        <div className="ti-mover-artist">{s.artist}</div>
                      </div>
                      <div className="ti-sell-gain">
                        <span className="ti-sell-pct">+{Math.round(s.pct)}%</span>
                        <small>up {tiMoney(s.gain)} vs paid</small>
                      </div>
                    </div>
                  ))}
                  <div className="ti-note">Records now worth well above what you paid — strong candidates if you ever sell.</div>
                </div>
              ) : (
                <div className="ti-quiet">No standout sell signals yet. Add what you paid for your records to surface them.</div>
              )}
            </div>

            <div className="ti-foot">Estimated market values from the price guide, at your records’ condition. Not an offer to buy or sell.</div>
          </div>
        )}
      </div>
    </div>
  );
}

// Shared so the /collection value bar shows the SAME "today" figure as the panel.
Object.assign(window, {
  TheIndex,
  BBRDailyIndex: { fetchDailySnaps: tiFetchDailySnaps, buildPortfolio, buildMovers, buildSellSignals },
});
