// theclaudetrader — Performance chart driven by Alpaca's portfolio_history (intraday to ALL)

const TIMEFRAMES = [
  { id: "1D",  label: "1D" },
  { id: "1W",  label: "1W" },
  { id: "1M",  label: "1M" },
  { id: "1Y",  label: "1Y" },
  { id: "ALL", label: "ALL" },
];

function PerformanceChart({ points: fallbackPoints, showBenchmark = true, liveValue = null, onTfChange = null, noFetch = false, fund = "llm" }) {
  const [tf, setTfInternal] = React.useState("1D");
  // Notify parent whenever tf changes so it can keep the linked AlphaChart in sync.
  const setTf = (next) => {
    setTfInternal(next);
    if (onTfChange) onTfChange(next);
  };
  const [hover, setHover] = React.useState(null);
  const [history, setHistory] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const svgRef = React.useRef(null);

  // Fetch per-timeframe so we keep the server-side bar granularity
  // (1W=15Min, 1M=1H, 1Y/ALL=1D). SPY benchmark is rebased server-side
  // at inception (see web/api/history.js) so different periods give
  // consistent "since inception" SPY numbers while the fund is younger
  // than the chosen window. Only the latest-bar timestamp differs by
  // a few hours between 1H and 1D timeframes — that's data freshness,
  // not a rebasing bug.
  React.useEffect(() => {
    // noFetch: the parent owns the data (e.g. a non-LLM fund whose history
    // doesn't live behind /api/history). Use the passed-in points directly.
    if (noFetch) return;
    let cancelled = false;
    const fetchHistory = async () => {
      setLoading(true);
      try {
        const r = await fetch(`/api/history?period=${tf}&fund=${fund}`, { cache: "no-store" });
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        const body = await r.json();
        if (!cancelled && body.points) setHistory(body.points);
      } catch (e) {
        if (!cancelled) setHistory(null);
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    fetchHistory();
    if (tf === "1D") {
      const id = setInterval(fetchHistory, 60000);
      return () => { cancelled = true; clearInterval(id); };
    }
    return () => { cancelled = true; };
  }, [tf, noFetch, fund]);

  const data = React.useMemo(() => {
    const src = history && history.length >= 2 ? history : (fallbackPoints || []);
    const mapped = src.map((p) => ({
      date: p.date,
      value: p.value,
      benchmark: p.benchmark ?? null,
    }));
    // Override the LAST point's NAV with the live account equity when provided.
    // Alpaca's portfolio_history equity series lags the real account equity by
    // hours-to-a-day after large fills (it reconstructs from base+profit_loss),
    // so the curve would otherwise end below the live header NAV and could look
    // like it trails SPY when it doesn't. Mirrors AlphaChart's same override.
    if (liveValue != null && mapped.length > 0) {
      mapped[mapped.length - 1] = { ...mapped[mapped.length - 1], value: liveValue };
    }
    return mapped;
  }, [history, fallbackPoints, liveValue]);

  // chart dimensions (viewBox — scales to container)
  const W = 1000, H = 340;
  const PAD = { t: 24, r: 12, b: 28, l: 56 };
  const innerW = W - PAD.l - PAD.r;
  const innerH = H - PAD.t - PAD.b;

  if (!data || data.length < 2) {
    return (
      <div style={{ height: 340, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontFamily: "var(--mono)", fontSize: 12 }}>
        {loading ? "Loading…" : `No ${tf} data yet — will populate as days accumulate`}
      </div>
    );
  }

  // Benchmark may not be present (intraday from Alpaca has no SPY series)
  const hasBenchmark = showBenchmark && data.every((d) => d.benchmark != null);

  // Visually rebase SPY to share NAV's starting y-value at the chart's left edge.
  // The header reports each series' true % return (NAV vs its own start, SPY vs
  // its inception anchor). The chart line's job is to make "who's ahead" visually
  // obvious over the selected window — without this scale, SPY anchored at
  // inception always looks higher in absolute dollars even when NAV outperforms
  // since the start of the window.
  const benchScale = hasBenchmark && data[0].benchmark
    ? data[0].value / data[0].benchmark
    : 1;
  const visualBench = (b) => (b == null ? null : b * benchScale);

  const valMin = Math.min(...data.map((d) => hasBenchmark ? Math.min(d.value, visualBench(d.benchmark)) : d.value));
  const valMax = Math.max(...data.map((d) => hasBenchmark ? Math.max(d.value, visualBench(d.benchmark)) : d.value));
  const range = valMax - valMin || 1;
  const yMin = valMin - range * 0.08;
  const yMax = valMax + range * 0.08;

  const x = (i) => PAD.l + (i / (data.length - 1)) * innerW;
  const y = (v) => PAD.t + (1 - (v - yMin) / (yMax - yMin)) * innerH;

  // Pre-computed paths
  const navPath = data.map((p, i) => `${i === 0 ? "M" : "L"} ${x(i).toFixed(2)} ${y(p.value).toFixed(2)}`).join(" ");
  const benchPath = hasBenchmark
    ? data.map((p, i) => `${i === 0 ? "M" : "L"} ${x(i).toFixed(2)} ${y(visualBench(p.benchmark)).toFixed(2)}`).join(" ")
    : "";
  const navArea = `${navPath} L ${x(data.length - 1).toFixed(2)} ${PAD.t + innerH} L ${x(0).toFixed(2)} ${PAD.t + innerH} Z`;

  // y-axis ticks — 4 ticks
  const ticks = [];
  for (let i = 0; i <= 4; i++) {
    const v = yMin + ((yMax - yMin) * i) / 4;
    ticks.push(v);
  }

  // x-axis ticks — evenly spaced labels
  const xTickCount = Math.min(6, data.length);
  const xTicks = [];
  for (let i = 0; i < xTickCount; i++) {
    const idx = Math.round((i * (data.length - 1)) / (xTickCount - 1));
    xTicks.push(idx);
  }

  const handleMove = (e) => {
    const rect = svgRef.current.getBoundingClientRect();
    const mx = ((e.clientX - rect.left) / rect.width) * W;
    const relX = Math.max(PAD.l, Math.min(W - PAD.r, mx));
    const t = (relX - PAD.l) / innerW;
    const idx = Math.round(t * (data.length - 1));
    setHover(idx);
  };
  const handleLeave = () => setHover(null);

  const start = data[0];
  const latest = data[data.length - 1];
  const viewing = hover !== null ? data[hover] : latest;
  // Show live NAV as the big number when not hovering (chart curve still uses the 5-min bars)
  const displayValue = hover === null && liveValue != null ? liveValue : viewing.value;
  const returnPct = (displayValue - start.value) / start.value;
  // SPY % must use SPY's own anchor (start.benchmark), not NAV's start.value.
  // With server-side rebasing, start.benchmark != start.value when the fund is
  // older than the window; using start.value as the SPY denominator was mixing
  // NAV and SPY series.
  const benchReturnPct = hasBenchmark && start.benchmark
    ? (viewing.benchmark - start.benchmark) / start.benchmark
    : null;

  // x-axis formatting — time-of-day for intraday, date for longer
  const isIntraday = tf === "1D";
  const fmtX = (d) => {
    const dt = new Date(d);
    return isIntraday
      ? dt.toLocaleTimeString("en-US", { hour: "numeric", hour12: true })
      : fmtDate(d, { style: "short" });
  };
  const headerLabel = isIntraday
    ? new Date(viewing.date).toLocaleString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true })
    : fmtDate(viewing.date, { style: "long" });

  return (
    <div>
      {/* Header: current value + timeframe selector */}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", flexWrap: "wrap", gap: 20, marginBottom: 20 }}>
        <div>
          <div className="micro" style={{ marginBottom: 8 }}>
            {hover !== null ? headerLabel : `Net asset value · ${headerLabel}`}
          </div>
          <div style={{ display: "flex", alignItems: "baseline", gap: 16, flexWrap: "wrap" }}>
            <div className="serif tnum" style={{ fontSize: "clamp(36px, 5vw, 54px)", lineHeight: 1 }}>
              {fmtUSD(displayValue, { decimals: 2 })}
            </div>
            <div className="tnum" style={{ fontSize: 16 }}>
              <span className={returnPct >= 0 ? "pos" : "neg"} style={{ fontWeight: 500 }}>
                {fmtPct(returnPct, { decimals: 2 })}
              </span>
              {hasBenchmark && (
                <span className="muted" style={{ marginLeft: 8 }}>
                  vs S&amp;P <span className={benchReturnPct >= 0 ? "pos" : "neg"}>{fmtPct(benchReturnPct, { decimals: 2, signed: false })}</span>
                </span>
              )}
              {loading && <span className="muted" style={{ marginLeft: 8, fontSize: 11 }}>· loading</span>}
            </div>
          </div>
        </div>
        <div style={{ display: "flex", gap: 6 }}>
          {TIMEFRAMES.map((t) => (
            <button key={t.id} className={cx("btn-ghost", tf === t.id && "active")} onClick={() => { setTf(t.id); setHover(null); }}>
              {t.label}
            </button>
          ))}
        </div>
      </div>

      <div className="chart-wrap">
        <svg
          ref={svgRef}
          viewBox={`0 0 ${W} ${H}`}
          className="chart-svg"
          preserveAspectRatio="none"
          onMouseMove={handleMove}
          onMouseLeave={handleLeave}
          onTouchMove={(e) => { if (e.touches[0]) handleMove(e.touches[0]); }}
          onTouchEnd={handleLeave}
          style={{ height: 340, cursor: "crosshair" }}
        >
          <defs>
            <linearGradient id="nav-fill" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor="var(--accent)" stopOpacity="0.18" />
              <stop offset="100%" stopColor="var(--accent)" stopOpacity="0" />
            </linearGradient>
          </defs>

          {/* y-grid */}
          {ticks.map((v, i) => (
            <g key={i}>
              <line x1={PAD.l} x2={W - PAD.r} y1={y(v)} y2={y(v)} stroke="var(--line)" strokeWidth="1" strokeDasharray={i === 0 ? "0" : "2 4"} />
              <text x={PAD.l - 10} y={y(v) + 4} fontSize="11" fill="var(--ink-4)" textAnchor="end" fontFamily="var(--mono)">
                {fmtUSD(v, { compact: true, decimals: 0 })}
              </text>
            </g>
          ))}

          {/* x-axis labels */}
          {xTicks.map((idx, i) => (
            <text key={i} x={x(idx)} y={H - 8} fontSize="11" fill="var(--ink-4)" textAnchor="middle" fontFamily="var(--mono)">
              {fmtX(data[idx].date)}
            </text>
          ))}

          {/* benchmark line (S&P 500) — dotted, brighter so it stays
              visible even when the fund and SPY diverge and the SPY line
              sits near the bottom of the y-range */}
          {hasBenchmark && (
            <path d={benchPath} fill="none" stroke="var(--ink-2, #9aa4b2)" strokeWidth="1.5" strokeDasharray="2 3" opacity="0.95" />
          )}

          {/* nav fill + line */}
          <path d={navArea} fill="url(#nav-fill)" />
          <path d={navPath} fill="none" stroke="var(--accent)" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />

          {/* hover crosshair */}
          {hover !== null && (
            <g>
              <line x1={x(hover)} x2={x(hover)} y1={PAD.t} y2={H - PAD.b} stroke="var(--ink-3)" strokeWidth="1" strokeDasharray="2 2" />
              <circle cx={x(hover)} cy={y(data[hover].value)} r="5" fill="var(--bg-card)" stroke="var(--accent)" strokeWidth="2" />
              {hasBenchmark && (
                <circle cx={x(hover)} cy={y(visualBench(data[hover].benchmark))} r="4" fill="var(--bg-card)" stroke="var(--ink-3)" strokeWidth="1.5" />
              )}
            </g>
          )}
        </svg>
      </div>

      <div style={{ display: "flex", gap: 20, marginTop: 12, fontSize: 12 }} className="muted">
        <div style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
          <span style={{ display: "inline-block", width: 14, height: 2, background: "var(--accent)" }} /> theclaudetrader
        </div>
        {hasBenchmark && (
          <div style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
            <span style={{ display: "inline-block", width: 14, height: 2, background: "var(--ink-4)", borderTop: "1px dashed var(--ink-4)" }} /> S&amp;P 500 (rebased)
          </div>
        )}
      </div>
    </div>
  );
}

// Alpha (NAV return - SPY return) plotted over the same series as the NAV chart.
// Reads the same /api/history endpoint, computes per-point alpha in pp, draws
// a single line color-coded by sign, with a zero baseline. Skips render if no
// benchmark data is available (e.g., intraday bars without SPY overlay).
function AlphaChart({ points: fallbackPoints, tf: tfProp, liveValue = null, fund = "llm" }) {
  // If a tf is passed in (linked from PerformanceChart), follow it; otherwise
  // own internal state. Linked mode hides the timeframe selector since the
  // user is already controlling it on the chart above.
  const [tfInternal, setTfInternal] = React.useState("1M");
  const tf = tfProp || tfInternal;
  const setTf = tfProp ? () => {} : setTfInternal;
  const showTimeframeSelector = !tfProp;
  const [hover, setHover] = React.useState(null);
  const [history, setHistory] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const svgRef = React.useRef(null);

  React.useEffect(() => {
    let cancelled = false;
    const fetchHistory = async () => {
      setLoading(true);
      try {
        const r = await fetch(`/api/history?period=${tf}&fund=${fund}`, { cache: "no-store" });
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        const body = await r.json();
        if (!cancelled && body.points) setHistory(body.points);
      } catch (e) {
        if (!cancelled) setHistory(null);
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    fetchHistory();
    return () => { cancelled = true; };
  }, [tf, fund]);

  const data = React.useMemo(() => {
    const src = history && history.length >= 2 ? history : (fallbackPoints || []);
    return src.filter((p) => p.benchmark != null);
  }, [history, fallbackPoints]);

  const W = 1000, H = 200;
  const PAD = { t: 16, r: 12, b: 28, l: 56 };
  const innerW = W - PAD.l - PAD.r;
  const innerH = H - PAD.t - PAD.b;

  if (!data || data.length < 2) {
    return (
      <div style={{ height: 200, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontFamily: "var(--mono)", fontSize: 12 }}>
        {loading ? "Loading…" : `Alpha vs S&P chart needs at least 2 points with benchmark — try 1M/1Y/ALL`}
      </div>
    );
  }

  // Compute alpha (in pp) at each point: alpha[i] = nav_return[i] - bench_return[i]
  // Override the LAST series point's NAV with liveValue (when provided) so the
  // alpha header matches what the NAV chart header shows — both should reflect
  // the same "now" NAV instead of the older last-historical-bar NAV.
  const v0 = data[0].value;
  const b0 = data[0].benchmark;
  const series = data.map((p, idx) => {
    const isLast = idx === data.length - 1;
    const navValue = isLast && liveValue != null ? liveValue : p.value;
    const navRet = navValue / v0 - 1;
    const benchRet = p.benchmark / b0 - 1;
    return { date: p.date, alpha_pp: (navRet - benchRet) * 100 };
  });

  const aMin = Math.min(0, ...series.map((s) => s.alpha_pp));
  const aMax = Math.max(0, ...series.map((s) => s.alpha_pp));
  const aRange = aMax - aMin || 1;
  const yMin = aMin - aRange * 0.12;
  const yMax = aMax + aRange * 0.12;

  const x = (i) => PAD.l + (i / (series.length - 1)) * innerW;
  const y = (v) => PAD.t + (1 - (v - yMin) / (yMax - yMin)) * innerH;

  const linePath = series.map((p, i) => `${i === 0 ? "M" : "L"} ${x(i).toFixed(2)} ${y(p.alpha_pp).toFixed(2)}`).join(" ");
  const yZero = y(0);

  const ticks = [yMin, (yMin + yMax) / 2, 0, yMax].filter((v, i, arr) => arr.indexOf(v) === i).sort((a, b) => a - b);

  const xTickCount = Math.min(6, series.length);
  const xTicks = [];
  for (let i = 0; i < xTickCount; i++) {
    const idx = Math.round((i * (series.length - 1)) / (xTickCount - 1));
    xTicks.push(idx);
  }

  const handleMove = (e) => {
    const rect = svgRef.current.getBoundingClientRect();
    const mx = ((e.clientX - rect.left) / rect.width) * W;
    const relX = Math.max(PAD.l, Math.min(W - PAD.r, mx));
    const t = (relX - PAD.l) / innerW;
    setHover(Math.round(t * (series.length - 1)));
  };

  const latest = series[series.length - 1];
  const viewing = hover !== null ? series[hover] : latest;
  const sign = viewing.alpha_pp >= 0 ? "pos" : "neg";
  const lineColor = latest.alpha_pp >= 0 ? "var(--accent)" : "var(--neg, #c0392b)";

  const fmtX = (d) => fmtDate(d, { style: "short" });

  return (
    <div style={{ marginTop: 24 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", flexWrap: "wrap", gap: 20, marginBottom: 12 }}>
        <div>
          <div className="micro" style={{ marginBottom: 6 }}>Alpha vs S&amp;P 500 · {fmtDate(viewing.date, { style: "long" })}</div>
          <div style={{ display: "flex", alignItems: "baseline", gap: 12, flexWrap: "wrap" }}>
            <div className="serif tnum" style={{ fontSize: "clamp(28px, 3.5vw, 40px)", lineHeight: 1 }}>
              <span className={sign}>{viewing.alpha_pp >= 0 ? "+" : ""}{viewing.alpha_pp.toFixed(2)}pp</span>
            </div>
            <div className="muted" style={{ fontSize: 12 }}>(NAV return − SPY return, pp = percentage points)</div>
          </div>
        </div>
        {showTimeframeSelector && (
          <div style={{ display: "flex", gap: 6 }}>
            {TIMEFRAMES.filter((t) => t.id !== "1D").map((t) => (
              <button key={t.id} className={cx("btn-ghost", tf === t.id && "active")} onClick={() => { setTf(t.id); setHover(null); }}>
                {t.label}
              </button>
            ))}
          </div>
        )}
        {!showTimeframeSelector && (
          <div className="muted" style={{ fontSize: 11, fontFamily: "var(--mono)" }}>
            {tf} · linked above
          </div>
        )}
      </div>

      <div className="chart-wrap">
        <svg
          ref={svgRef}
          viewBox={`0 0 ${W} ${H}`}
          className="chart-svg"
          preserveAspectRatio="none"
          onMouseMove={handleMove}
          onMouseLeave={() => setHover(null)}
          onTouchMove={(e) => { if (e.touches[0]) handleMove(e.touches[0]); }}
          onTouchEnd={() => setHover(null)}
          style={{ height: 200, cursor: "crosshair" }}
        >
          {ticks.map((v, i) => (
            <g key={i}>
              <line x1={PAD.l} x2={W - PAD.r} y1={y(v)} y2={y(v)} stroke="var(--line)" strokeWidth={Math.abs(v) < 0.001 ? 1.5 : 1} strokeDasharray={Math.abs(v) < 0.001 ? "0" : "2 4"} opacity={Math.abs(v) < 0.001 ? 0.6 : 1} />
              <text x={PAD.l - 10} y={y(v) + 4} fontSize="11" fill="var(--ink-4)" textAnchor="end" fontFamily="var(--mono)">
                {v >= 0 ? "+" : ""}{v.toFixed(1)}pp
              </text>
            </g>
          ))}

          {xTicks.map((idx, i) => (
            <text key={i} x={x(idx)} y={H - 8} fontSize="11" fill="var(--ink-4)" textAnchor="middle" fontFamily="var(--mono)">
              {fmtX(series[idx].date)}
            </text>
          ))}

          <path d={linePath} fill="none" stroke={lineColor} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />

          {hover !== null && (
            <g>
              <line x1={x(hover)} x2={x(hover)} y1={PAD.t} y2={H - PAD.b} stroke="var(--ink-3)" strokeWidth="1" strokeDasharray="2 2" />
              <circle cx={x(hover)} cy={y(series[hover].alpha_pp)} r="5" fill="var(--bg-card)" stroke={lineColor} strokeWidth="2" />
            </g>
          )}
        </svg>
      </div>
    </div>
  );
}

Object.assign(window, { PerformanceChart, AlphaChart });
