// ===========================================================
// The Lead-In — per-album value-over-time chart (Brief B).
//
// Renders inside the collection item detail modal, below the core facts. Reads
// the valuation_snapshots time series (Brief A) for ONE collection item and
// draws a calm, editorial inline-SVG line chart. No build step, no chart lib:
// hand-rolled SVG styled entirely with the brand tokens.
//
// THE NON-NEGOTIABLE SPLIT (no-fabrication promise):
//   - is_estimated = false  -> SOLID line in --accent (vermillion): a real
//                              weekly print of the value the engine computed.
//   - is_estimated = true   -> DASHED line in --ink-mute (muted): the modelled
//                              backfill. STEP 0 cleared no real historical sold
//                              source, so the backfill is a flat carry-back; it
//                              must never read as realised market movement.
// A segment is drawn solid only when BOTH its endpoints are real, so the bridge
// from the estimated past into the live present stays dashed (honest).
//
// Data: window.BBR_supabase. Owner-only RLS on valuation_snapshots means a
// member only ever reads snapshots for items they own; nothing else is reachable
// from the client. Market value only (purchase cost never enters a snapshot).
//
// Globals in: React, window.BBR_supabase. Exposes window.ValueHistoryChart.
// ===========================================================
const {
  useState: useVHState, useEffect: useVHEffect, useMemo: useVHMemo, useRef: useVHRef,
} = React;

// ---- formatting helpers ----------------------------------------------------
const VH_SYM = { GBP: "£", USD: "$", EUR: "€" };
function vhSym(c) { return VH_SYM[c] || ""; }
// Money for the chart/headline. Whole pounds keep typical values calm, but a
// non-integer value under £100 shows pence — so cheap records render honestly
// (and the y-axis labels don't collapse to duplicate rounded values).
function vhMoney(n, ccy) {
  if (n === null || n === undefined || !isFinite(n)) return "—";
  const s = vhSym(ccy);
  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 s ? s + v : (ccy || "") + " " + v;
}
// Y-axis tick label. Precision comes from the gap between ticks so labels are
// always distinct and "nice" (whole pounds for normal ranges, decimals only
// when the range is tight) — never raw padded bounds with stray pence.
function vhAxis(v, ccy, gap) {
  const s = vhSym(ccy);
  const dec = gap >= 2 ? 0 : (gap >= 0.2 ? 1 : 2);
  const str = Number(v).toLocaleString("en-GB", { minimumFractionDigits: dec, maximumFractionDigits: dec });
  return s ? s + str : (ccy || "") + " " + str;
}
const VH_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const VH_DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// "May ’26" — month + 2-digit year, for sparse x-axis labels.
function vhMonthLabel(t) {
  const d = new Date(t);
  return VH_MONTHS[d.getMonth()] + " ’" + String(d.getFullYear()).slice(2);
}
// "12 May 2026" — full date for the tooltip.
function vhFullDate(t) {
  const d = new Date(t);
  return d.getDate() + " " + VH_MONTHS[d.getMonth()] + " " + d.getFullYear();
}
// Next weekly capture: the cron runs Mondays 08:00 UTC. Used by the empty state.
function vhNextRun() {
  const now = new Date();
  const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 8, 0, 0));
  let add = (1 - d.getUTCDay() + 7) % 7;       // days until the next Monday
  if (add === 0 && now.getTime() >= d.getTime()) add = 7; // today is Mon but past 08:00
  d.setUTCDate(d.getUTCDate() + add);
  return VH_DAYS[d.getUTCDay()] + " " + d.getUTCDate() + " " + VH_MONTHS[d.getUTCMonth()];
}

// Plain-language confidence explainers (shown on tap/hover near the chip).
const VH_CONF_TEXT = {
  high: "Based on a pressing-level price guide at your record’s condition.",
  medium: "Based on the release generally, not this exact pressing, or an approximated condition.",
  low: "Based on a single source (the lowest listing scaled to your grade).",
};

// ---- the component ---------------------------------------------------------
// props: { itemId }  — the collection_items.id whose history to chart.
function ValueHistoryChart({ itemId }) {
  const [snaps, setSnaps] = useVHState(undefined); // undefined = loading, [] = none
  const [width, setWidth] = useVHState(600);       // measured plot width (px)
  const [hover, setHover] = useVHState(null);      // hovered snapshot index
  const [confOpen, setConfOpen] = useVHState(false);
  const boxRef = useVHRef(null);
  const svgRef = useVHRef(null);

  // Fetch this item's snapshots, oldest-first. RLS scopes to the owner.
  useVHEffect(() => {
    let active = true;
    setSnaps(undefined);
    if (!itemId || !window.BBR_supabase) { setSnaps([]); return; }
    window.BBR_supabase
      .from("valuation_snapshots")
      .select("captured_at,value,currency,is_estimated,confidence,granularity,source_count")
      .eq("collection_item_id", itemId)
      .order("captured_at", { ascending: true })
      .then(({ data }) => {
        if (!active) return;
        const rows = (Array.isArray(data) ? data : [])
          .filter((r) => r && r.value !== null && r.value !== undefined && isFinite(Number(r.value)));
        setSnaps(rows);
      })
      .catch(() => { if (active) setSnaps([]); });
    return () => { active = false; };
  }, [itemId]);

  // Track container width so the SVG renders at 1:1 px (crisp text, real 44px+
  // hit area) and reflows on resize / rotation rather than overflowing.
  useVHEffect(() => {
    const el = boxRef.current;
    if (!el) return;
    const measure = () => setWidth(Math.max(220, 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); };
  }, [snaps === undefined]);

  // ---- geometry + series facts (memoised) ----------------------------------
  const H = 220, padL = 46, padR = 14, padT = 16, padB = 30;
  const view = useVHMemo(() => {
    if (!Array.isArray(snaps) || !snaps.length) return null;
    const innerW = Math.max(10, width - padL - padR);
    const innerH = H - padT - padB;
    const n = snaps.length;

    // Y domain. A flat series (e.g. the estimated backfill) pads around its
    // single value so the line sits mid-chart rather than on an edge.
    const vals = snaps.map((s) => Number(s.value));
    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.12); lo -= p; hi += p; }
    else { const p = (hi - lo) * 0.12; lo -= p; hi += p; }
    if (lo < 0) lo = 0;
    const span = (hi - lo) || 1;

    // X domain (time). Single point is centred.
    const t0 = new Date(snaps[0].captured_at).getTime();
    const t1 = new Date(snaps[n - 1].captured_at).getTime();
    const tspan = (t1 - t0) || 1;
    const X = (t) => n === 1 ? padL + innerW / 2 : padL + ((new Date(t).getTime() - t0) / tspan) * innerW;
    const Y = (v) => padT + (1 - (v - lo) / span) * innerH;

    const pts = snaps.map((s) => ({ x: X(s.captured_at), y: Y(Number(s.value)), s }));

    // Segments: solid only when BOTH endpoints are real prints.
    const segs = [];
    for (let i = 1; i < pts.length; i++) {
      const live = !snaps[i - 1].is_estimated && !snaps[i].is_estimated;
      segs.push({ live, a: pts[i - 1], b: pts[i] });
    }

    // Y gridlines: low / mid / high of the domain.
    const yTicks = [lo, (lo + hi) / 2, hi].map((v) => ({ v, y: Y(v) }));

    // X labels: sparse so they never overlap or overflow (fewer on mobile).
    const maxLabels = width < 360 ? 3 : (width < 560 ? 4 : 5);
    const step = Math.max(1, Math.ceil(n / maxLabels));
    const xTicks = [];
    for (let i = 0; i < n; i += step) xTicks.push({ x: pts[i].x, t: snaps[i].captured_at });
    const last = pts[n - 1];
    if (!xTicks.length || xTicks[xTicks.length - 1].x < last.x - 1) xTicks.push({ x: last.x, t: snaps[n - 1].captured_at });

    return { innerW, innerH, lo, hi, pts, segs, yTicks, xTicks, n };
  }, [snaps, width]);

  // ---- series-level facts for the header (delta / chip / note) -------------
  const facts = useVHMemo(() => {
    if (!Array.isArray(snaps) || !snaps.length) return null;
    const n = snaps.length;
    const cur = snaps[n - 1];
    const reals = snaps.filter((s) => !s.is_estimated);
    const ccy = cur.currency || "GBP";

    // Headline delta: real-vs-real where possible; else flag "vs estimated".
    let delta = null;
    if (n >= 2) {
      let base, label, vsEst;
      if (reals.length >= 2) { base = reals[0]; vsEst = false; label = "since " + vhMonthLabel(reals[0].captured_at); }
      else { base = snaps[0]; vsEst = true; label = "vs estimated"; }
      const d = Number(cur.value) - Number(base.value);
      const pct = Number(base.value) ? (d / Number(base.value)) * 100 : null;
      const dir = Math.abs(d) < 0.005 ? "flat" : (d > 0 ? "up" : "down");
      delta = { d, pct, label, vsEst, dir, ccy };
    }

    return {
      cur, ccy,
      onlyEstimated: reals.length === 0,
      hasBoth: reals.length > 0 && reals.length < n,
      confidence: cur.confidence || null,
      granularity: cur.granularity || null,
      delta,
      single: n === 1,
    };
  }, [snaps]);

  // ---- nearest-point hover (whole plot is the target; snaps to nearest) ----
  function onMove(e) {
    if (!view || !svgRef.current) return;
    const rect = svgRef.current.getBoundingClientRect();
    const cx = (e.touches && e.touches[0] ? e.touches[0].clientX : e.clientX) - rect.left;
    let best = 0, bd = Infinity;
    for (let i = 0; i < view.pts.length; i++) {
      const d = Math.abs(view.pts[i].x - cx);
      if (d < bd) { bd = d; best = i; }
    }
    setHover(best);
  }
  function onLeave() { setHover(null); }

  // ---- render --------------------------------------------------------------
  if (snaps === undefined) {
    return (
      <div className="vh">
        <div className="vh-eyebrow">Value over time</div>
        <div className="vh-loading">Loading value history…</div>
      </div>
    );
  }

  // No snapshots yet: tracking has not started for this item.
  if (!snaps.length) {
    return (
      <div className="vh">
        <div className="vh-eyebrow">Value over time</div>
        <div className="vh-empty">
          <div className="vh-empty-t">Tracking starts soon</div>
          <div className="vh-empty-s">First valuation due {vhNextRun()}.</div>
        </div>
      </div>
    );
  }

  const f = facts;
  const hov = hover != null && view ? view.pts[hover] : null;
  // Tooltip x clamped within the plot so it never overflows the card edge.
  const tipLeft = hov ? Math.max(8, Math.min(width - 8, hov.x)) : 0;

  return (
    <div className="vh">
      <div className="vh-eyebrow">Value over time</div>

      {/* Headline: current value + windowed delta */}
      <div className="vh-head">
        <div className="vh-current">
          <span className="vh-cur-num">{vhMoney(f.cur.value, f.ccy)}</span>
          <span className="vh-cur-lab">{f.onlyEstimated ? "estimated value" : "current value"}</span>
        </div>
        {f.delta && !(f.onlyEstimated && f.delta.dir === "flat") && (
          <div className={"vh-delta " + f.delta.dir}>
            <span className="vh-delta-num">
              {f.delta.dir === "up" ? "+" : f.delta.dir === "down" ? "−" : ""}
              {vhMoney(Math.abs(f.delta.d), f.ccy)}
              {f.delta.pct !== null && (
                <span className="vh-delta-pct">
                  {" "}({f.delta.dir === "up" ? "+" : f.delta.dir === "down" ? "−" : ""}
                  {Math.abs(f.delta.pct).toFixed(1)}%)
                </span>
              )}
            </span>
            <span className="vh-delta-lab">{f.delta.label}</span>
          </div>
        )}
      </div>

      {/* Metadata row: confidence chip (tap for explainer) + master note */}
      <div className="vh-meta">
        {f.confidence && (
          <button
            type="button"
            className={"vh-chip is-" + f.confidence}
            aria-expanded={confOpen}
            title={VH_CONF_TEXT[f.confidence] || ""}
            onClick={() => setConfOpen((o) => !o)}
          >
            <span className="vh-chip-dot" />
            {f.confidence} confidence
          </button>
        )}
      </div>
      {confOpen && f.confidence && (
        <div className="vh-explain">{VH_CONF_TEXT[f.confidence]}</div>
      )}
      {f.granularity === "master" && (
        <div className="vh-note">This estimate is for the release in general, not your specific pressing.</div>
      )}

      {/* Chart */}
      <div className="vh-chartbox" ref={boxRef}>
        {f.single ? (
          <div className="vh-single">
            <div className={"vh-single-dot" + (f.cur.is_estimated ? " est" : "")} aria-hidden="true" />
            <div className="vh-single-t">
              {f.cur.is_estimated ? "One estimated reading so far" : "One reading so far"}, captured {vhFullDate(f.cur.captured_at)}.
            </div>
            <div className="vh-single-s">A line will appear once the next weekly valuation lands.</div>
          </div>
        ) : (
          <svg
            ref={svgRef}
            className="vh-svg"
            width={width}
            height={H}
            viewBox={"0 0 " + width + " " + H}
            role="img"
            aria-label="Line chart of this record’s estimated value over time"
            onPointerMove={onMove}
            onPointerDown={onMove}
            onPointerLeave={onLeave}
            onTouchStart={onMove}
            onTouchMove={onMove}
            onTouchEnd={onLeave}
          >
            {/* Y gridlines + labels */}
            {view.yTicks.map((t, i) => (
              <g key={"y" + i}>
                <line className="vh-grid" x1={padL} y1={t.y} x2={width - padR} y2={t.y} />
                <text className="vh-ylab" x={padL - 8} y={t.y + 3} textAnchor="end">
                  {vhAxis(t.v, f.ccy, (view.hi - view.lo) / 2)}
                </text>
              </g>
            ))}

            {/* X labels */}
            {view.xTicks.map((t, i) => (
              <text key={"x" + i} className="vh-xlab" x={t.x} y={H - 10}
                textAnchor={i === 0 ? "start" : (i === view.xTicks.length - 1 ? "end" : "middle")}>
                {vhMonthLabel(t.t)}
              </text>
            ))}

            {/* Line segments: solid (live) vs dashed (estimated) */}
            {view.segs.map((sg, i) => (
              <line key={"s" + i}
                className={sg.live ? "vh-line-live" : "vh-line-est"}
                x1={sg.a.x} y1={sg.a.y} x2={sg.b.x} y2={sg.b.y} />
            ))}

            {/* Dots on the real (live) points only — the meaningful data */}
            {view.pts.map((p, i) => (
              !p.s.is_estimated ? <circle key={"d" + i} className="vh-dot-live" cx={p.x} cy={p.y} r="3" /> : null
            ))}

            {/* Hover crosshair + active point */}
            {hov && (
              <g>
                <line className="vh-cross" x1={hov.x} y1={padT} x2={hov.x} y2={H - padB} />
                <circle className={hov.s.is_estimated ? "vh-active est" : "vh-active live"} cx={hov.x} cy={hov.y} r="5" />
              </g>
            )}
          </svg>
        )}

        {/* Tooltip (HTML, positioned over the plot) */}
        {hov && !f.single && (
          <div className="vh-tip" style={{ left: tipLeft + "px" }}>
            <div className="vh-tip-v">{vhMoney(hov.s.value, hov.s.currency || f.ccy)}</div>
            <div className="vh-tip-d">{vhFullDate(hov.s.captured_at)}</div>
            <div className={"vh-tip-tag " + (hov.s.is_estimated ? "est" : "live")}>
              {hov.s.is_estimated ? "Estimated" : "Live valuation"}
            </div>
          </div>
        )}
      </div>

      {/* Legend / caption */}
      {!f.single && f.hasBoth && (
        <div className="vh-legend">
          <span className="vh-leg"><span className="vh-swatch live" /> Live valuation</span>
          <span className="vh-leg"><span className="vh-swatch est" /> Estimated</span>
        </div>
      )}
      {!f.single && f.onlyEstimated && (
        <div className="vh-caption">Pre-launch history is estimated. Live weekly valuations begin from launch.</div>
      )}
    </div>
  );
}

Object.assign(window, { ValueHistoryChart });
