// running/src/performanceView.jsx — slice 4 of the module split.
//
// The Performance tab: Garmin VO₂max / Endurance Score / Hill Score
// history with per-metric target lines (localStorage-persisted) and
// moving-average + linear-trend overlays. Extracted verbatim from
// running/index.html. window.RaceMath (UMD lib) seeds the VO₂max
// target from the active plan's goal time.

const { C, Card, Stat, EmptyState, ChartTooltip } = window.SharedUI;
const useData = () => (window.__appUseData ? window.__appUseData() : null);
const { useState, useMemo } = React;
const {
  Area, Line, ComposedChart, XAxis, YAxis, CartesianGrid, Tooltip,
  ResponsiveContainer, ReferenceLine,
} = Recharts;

    function PerformanceView() {
      const { healthEntries, garminStats, activePlan } = useData();
      const [tf, setTf] = useState("all");
      // Per-metric target lines, persisted on the device. VO₂max also has
      // a goal-derived default (see marathonVO2 below) when left blank.
      const TARGETS_KEY = "cr_perf_targets";
      const [targets, setTargets] = useState(() => {
        try { return JSON.parse(localStorage.getItem(TARGETS_KEY)) || {}; }
        catch (e) { return {}; }
      });
      const setTarget = (key, raw) => {
        setTargets(prev => {
          const next = { ...prev };
          const n = parseFloat(raw);
          if (raw === "" || !Number.isFinite(n)) delete next[key];
          else next[key] = n;
          try { localStorage.setItem(TARGETS_KEY, JSON.stringify(next)); } catch (e) {}
          return next;
        });
      };
      const fmtD = (iso) => new Date(iso + "T00:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric" });
      const fmtScore = (v) => v == null ? "--" : (Number(v) % 1 === 0 ? String(Number(v)) : Number(v).toFixed(1));
      const TF = [
        { k: "1m", label: "1M", days: 30 },
        { k: "3m", label: "3M", days: 90 },
        { k: "6m", label: "6M", days: 180 },
        { k: "1y", label: "1Y", days: 365 },
        { k: "all", label: "All", days: null },
      ];
      const tfDays = (TF.find(t => t.k === tf) || {}).days;
      const cutoffIso = tfDays ? new Date(Date.now() - tfDays * 864e5).toISOString().slice(0, 10) : null;
      const series = (key, min) => (healthEntries || [])
        .filter(h => h && h.date && Number.isFinite(Number(h[key])) && Number(h[key]) > (min || 0))
        .sort((a, b) => a.date.localeCompare(b.date))
        .map(h => ({ date: fmtD(h.date), iso: h.date, value: parseFloat(Number(h[key]).toFixed(1)) }));
      // Goal-race VO₂max target — Daniels VDOT of the active plan's goal
      // time. Seeds the VO₂ Max chart's target line unless the user has
      // typed their own override.
      const marathonVO2 = useMemo(() => {
        const dist = activePlan && activePlan.raceDistance;
        const gt = activePlan && activePlan.goalTime;
        if (!dist || !gt || !window.RaceMath || !window.RaceMath.estimateVO2max) return null;
        const p = String(gt).replace(/^sub[- ]?/i, "").split(":").map(x => parseInt(x, 10));
        if (p.some(x => !Number.isFinite(x))) return null;
        const sec = p.length === 3 ? p[0] * 3600 + p[1] * 60 + p[2]
                  : p.length === 2 ? p[0] * 3600 + p[1] * 60 : 0;
        const meters = { "5K": 5000, "10K": 10000, "Half Marathon": 21097, "Marathon": 42195 }[dist];
        if (!sec || !meters) return null;
        const v = window.RaceMath.estimateVO2max(meters, sec);
        return v ? parseFloat(v.toFixed(1)) : null;
      }, [activePlan]);
      const metrics = [
        { key: "garminVO2max", min: 30, label: "VO₂ Max", unit: "ml/kg/min", color: C.cyan, snap: garminStats?.vo2Max,
          blurb: "Aerobic ceiling — Garmin Firstbeat estimate. One precise reading is recorded per sync, so the trend fills in day by day." },
        { key: "enduranceScore", min: 0, label: "Endurance Score", unit: "", color: C.green, snap: garminStats?.enduranceScore,
          blurb: "Ability to sustain effort over time — rises with consistent volume." },
        { key: "hillScore", min: 0, label: "Hill Score", unit: "", color: C.purple || "#a78bfa", snap: garminStats?.hillScore,
          blurb: "Climbing strength & endurance on inclines." },
      ].map(m => {
        const all = series(m.key, m.min);
        const s = cutoffIso ? all.filter(p => p.iso >= cutoffIso) : all;
        // Current value is the latest reading ever (or the snapshot) —
        // independent of the selected timeframe. The timeframe only
        // scopes the trend chart and its delta.
        const current = all.length ? all[all.length - 1].value : (m.snap != null ? parseFloat(Number(m.snap).toFixed(1)) : null);
        const first = s.length ? s[0].value : null;
        const lastInRange = s.length ? s[s.length - 1].value : null;
        const delta = (lastInRange != null && first != null) ? parseFloat((lastInRange - first).toFixed(1)) : null;
        const unavailable = all.length === 0 && m.snap == null;
        // Overlay series: least-squares linear trend + moving average
        // (window scales with point count, ~30 for a full year).
        const n = s.length;
        let chart = s;
        if (n >= 2) {
          const ys = s.map(p => p.value);
          const mx = (n - 1) / 2, my = ys.reduce((a, b) => a + b, 0) / n;
          let num = 0, den = 0;
          for (let i = 0; i < n; i++) { num += (i - mx) * (ys[i] - my); den += (i - mx) * (i - mx); }
          const slope = den ? num / den : 0, intercept = my - slope * mx;
          const win = Math.max(2, Math.min(30, Math.round(n / 6)));
          chart = s.map((p, i) => {
            const lo = Math.max(0, i - win + 1);
            let sum = 0; for (let j = lo; j <= i; j++) sum += ys[j];
            return { ...p,
              ma: parseFloat((sum / (i - lo + 1)).toFixed(2)),
              trend: parseFloat((slope * i + intercept).toFixed(2)) };
          });
        }
        return { ...m, all, s, chart, current, delta, unavailable };
      });
      const anyData = metrics.some(m => !m.unavailable);
      return (
        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <div>
            <div style={{ fontFamily: "'Space Grotesk', sans-serif", fontSize: 26, fontWeight: 800, color: C.text, marginBottom: 4 }}>Performance</div>
            <div style={{ color: C.textMuted, fontSize: 13 }}>VO₂ max, Endurance &amp; Hill score — accumulated daily with decimal precision</div>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(170px,1fr))", gap: 16 }}>
            {metrics.map(m => (
              <Card key={m.key}>
                <Stat label={m.label} value={fmtScore(m.current)} unit={m.unit} color={m.color} />
                <div style={{ fontSize: 11, color: C.textMuted, marginTop: 6 }}>
                  {m.unavailable ? "Not reported by your Garmin device"
                    : m.delta == null ? (m.s.length === 1 ? "1 reading — trend builds with each sync" : "No readings in range")
                    : m.delta === 0 ? `Flat over ${m.s.length} readings`
                    : `${m.delta > 0 ? "▲ +" : "▼ "}${fmtScore(m.delta)} over ${m.s.length} readings`}
                </div>
              </Card>
            ))}
          </div>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
            {TF.map(t => (
              <button key={t.k} onClick={() => setTf(t.k)}
                style={{ padding: "5px 12px", borderRadius: 6, fontSize: 12, cursor: "pointer",
                  fontFamily: "'IBM Plex Mono',monospace",
                  border: `1px solid ${tf === t.k ? C.cyan : C.border}`,
                  background: tf === t.k ? C.cyan + "1f" : "transparent",
                  color: tf === t.k ? C.cyan : C.textMuted }}>
                {t.label}
              </button>
            ))}
          </div>
          {!anyData ? (
            <Card>
              <EmptyState icon="📈" title="No performance data yet"
                sub="Run the Garmin sync (Settings → Garmin). Each sync records the current precise VO₂ max — the trend chart fills in as daily readings accumulate." />
            </Card>
          ) : metrics.filter(m => !m.unavailable).map(m => {
            const tgt = m.key === "garminVO2max"
              ? (targets.garminVO2max != null ? targets.garminVO2max : marathonVO2)
              : (targets[m.key] != null ? targets[m.key] : null);
            const tgtIsAuto = m.key === "garminVO2max" && targets.garminVO2max == null && marathonVO2 != null;
            return (
            <Card key={m.key}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, marginBottom: 2 }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: C.textDim }}>{m.label} — History</div>
                <div style={{ display: "flex", alignItems: "center", gap: 5, fontSize: 11, color: C.textMuted }}>
                  <span>🎯</span>
                  <input type="number" inputMode="decimal" value={targets[m.key] != null ? targets[m.key] : ""}
                    placeholder={m.key === "garminVO2max" && marathonVO2 != null ? String(marathonVO2) : "target"}
                    onChange={e => setTarget(m.key, e.target.value)}
                    style={{ width: 62, background: C.bg2, border: `1px solid ${C.border}`, borderRadius: 5,
                      padding: "3px 7px", color: C.text, fontSize: 11, textAlign: "right", fontFamily: "'IBM Plex Mono',monospace" }} />
                </div>
              </div>
              <div style={{ fontSize: 11, color: C.textMuted, marginBottom: 8, lineHeight: 1.5 }}>{m.blurb}</div>
              {m.s.length < 2 ? (
                <EmptyState icon="🗓️" title={m.s.length === 1 ? "Only one reading so far" : "No readings in this range"}
                  sub={m.s.length === 1
                    ? "The trend line appears once a second daily reading is recorded — sync again tomorrow."
                    : "Widen the timeframe above, or sync again to add readings."} />
              ) : (<>
                <div style={{ display: "flex", gap: 13, flexWrap: "wrap", fontSize: 10, marginBottom: 8 }}>
                  <span style={{ color: m.color }}>● actual</span>
                  <span style={{ color: C.amber }}>● 30-day avg</span>
                  <span style={{ color: C.textDim }}>● trend</span>
                  {tgt != null && <span style={{ color: C.text }}>● target {fmtScore(tgt)}{tgtIsAuto ? " · from race goal" : ""}</span>}
                </div>
                <ResponsiveContainer width="100%" height={210}>
                  <ComposedChart data={m.chart}>
                    <CartesianGrid strokeDasharray="3 3" stroke={C.border} vertical={false} />
                    <XAxis dataKey="date" tick={{ fill: C.textMuted, fontSize: 10 }} axisLine={false} tickLine={false} interval={Math.max(0, Math.floor(m.chart.length / 8))} />
                    <YAxis tick={{ fill: C.textMuted, fontSize: 11 }} axisLine={false} tickLine={false}
                      domain={[dMin => tgt != null ? Math.min(dMin, tgt) : dMin, dMax => tgt != null ? Math.max(dMax, tgt) : dMax]} />
                    <Tooltip content={<ChartTooltip />} />
                    {tgt != null && <ReferenceLine y={tgt} stroke={C.text} strokeDasharray="5 4" strokeWidth={1.5} strokeOpacity={0.85} />}
                    <Area type="monotone" dataKey="value" stroke={m.color} fill={m.color + "20"} strokeWidth={2} name={m.label} dot={(tf === "1m" || tf === "3m") ? { r: 2.5, fill: m.color, strokeWidth: 0 } : false} connectNulls isAnimationActive={false} />
                    <Line type="monotone" dataKey="ma" stroke={C.amber} strokeWidth={2} dot={false} name="30-day avg" connectNulls isAnimationActive={false} />
                    <Line type="monotone" dataKey="trend" stroke={C.textDim} strokeWidth={1.5} strokeDasharray="6 4" dot={false} name="Trend" connectNulls isAnimationActive={false} />
                  </ComposedChart>
                </ResponsiveContainer>
              </>)}
            </Card>
            );
          })}
        </div>
      );
    }

window.AppViews = Object.assign(window.AppViews || {}, { PerformanceView });
