/* ============================================================
   screens-bike.jsx — BIKE: rides detected from precise OwnTracks GPS,
   with delayed Find My bike-tag corroboration. Distance/speed/
   calories come from that trip record; HR (avg/max/zones) is
   computed by openclawclinic from its own raw_hr stream, time-
   aligned to the ride window. Fetches its own endpoints
   (/api/bike/summary, /api/bike/rides/{id}) rather than reading
   the shared dashboard snapshot — ride volume/cadence doesn't fit
   the once-daily assembled-snapshot contract the rest of the app uses.
   ============================================================ */
(function () {
  const { useState, useEffect, useCallback } = React;

  const ZONE_COLORS = [
    "var(--ink-4)", "var(--c-line)", "var(--attn)",
    "color-mix(in oklch, var(--attn) 45%, var(--acute))", "var(--acute)",
  ];
  const ZONE_KEYS = ["z1", "z2", "z3", "z4", "z5"];

  function zoneColor(bpm, maxHrUsed) {
    if (bpm == null || !maxHrUsed) return "var(--ink-4)";
    const frac = bpm / maxHrUsed;
    const idx = frac < 0.6 ? 0 : frac < 0.7 ? 1 : frac < 0.8 ? 2 : frac < 0.9 ? 3 : 4;
    return ZONE_COLORS[idx];
  }

  function fmtDuration(min) {
    if (min == null) return "—";
    return min < 60 ? `${Math.round(min)}m` : `${(min / 60).toFixed(1)}h`;
  }

  function fmtRideDate(iso) {
    const d = new Date(iso);
    return d.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" }) +
      " · " + d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
  }

  // ---- route: Web Mercator street map, HR-zone-colored ----
  function mercatorPoint(point) {
    const lat = Math.max(-85.05112878, Math.min(85.05112878, point.lat));
    const sin = Math.sin((lat * Math.PI) / 180);
    return {
      x: (point.lon + 180) / 360,
      y: 0.5 - Math.log((1 + sin) / (1 - sin)) / (4 * Math.PI),
      bpm: point.bpm,
    };
  }

  function projectTrack(track, w, h, pad) {
    if (!track || track.length < 2) return null;
    const mercator = track.map(mercatorPoint);
    const xs = mercator.map((p) => p.x);
    const ys = mercator.map((p) => p.y);
    const xlo = Math.min(...xs), xhi = Math.max(...xs);
    const ylo = Math.min(...ys), yhi = Math.max(...ys);
    const spanX = Math.max(xhi - xlo, 1e-12), spanY = Math.max(yhi - ylo, 1e-12);
    let zoom = 18;
    while (zoom > 2) {
      const world = 256 * (2 ** zoom);
      if (spanX * world <= w - pad * 2 && spanY * world <= h - pad * 2) break;
      zoom -= 1;
    }
    const scale = 256 * (2 ** zoom);
    const cx = (xlo + xhi) / 2, cy = (ylo + yhi) / 2;
    const left = cx * scale - w / 2;
    const top = cy * scale - h / 2;
    const points = mercator.map((p) => ({
      x: p.x * scale - left,
      y: p.y * scale - top,
      bpm: p.bpm,
    }));
    const tiles = [];
    const tileCount = 2 ** zoom;
    for (let ty = Math.floor(top / 256); ty <= Math.floor((top + h) / 256); ty += 1) {
      if (ty < 0 || ty >= tileCount) continue;
      for (let tx = Math.floor(left / 256); tx <= Math.floor((left + w) / 256); tx += 1) {
        const tileX = ((tx % tileCount) + tileCount) % tileCount;
        tiles.push({
          key: `${zoom}/${tileX}/${ty}`,
          href: `/api/bike/map-tiles/${zoom}/${tileX}/${ty}.png`,
          x: tx * 256 - left,
          y: ty * 256 - top,
        });
      }
    }
    return { points, tiles };
  }

  function RouteMap({ track, maxHrUsed, w = 320, h = 260 }) {
    const projected = projectTrack(track, w, h, 24);
    if (!projected) {
      return (
        <div style={{ padding: "28px 12px", textAlign: "center", color: "var(--ink-4)", fontSize: 12.5,
                      background: "var(--bg-sunken)", borderRadius: "var(--r-sm)", border: "1px solid var(--line)" }}>
          No GPS track for this ride.
        </div>
      );
    }
    const { points: pts, tiles } = projected;
    return (
      <div style={{ position: "relative", overflow: "hidden", borderRadius: "var(--r-sm)", border: "1px solid var(--line)" }}>
        <svg viewBox={`0 0 ${w} ${h}`} width="100%" height={h}
             style={{ display: "block", background: "var(--bg-sunken)" }}>
          {tiles.map((tile) => (
            <image key={tile.key} href={tile.href} x={tile.x} y={tile.y} width="256" height="256" />
          ))}
          {pts.slice(1).map((p, i) => {
            const prev = pts[i];
            return <line key={`halo-${i}`} x1={prev.x} y1={prev.y} x2={p.x} y2={p.y} stroke="white" strokeOpacity="0.8" strokeWidth="6" strokeLinecap="round" />;
          })}
          {pts.slice(1).map((p, i) => {
            const prev = pts[i];
            const col = zoneColor(p.bpm ?? prev.bpm, maxHrUsed);
            return <line key={i} x1={prev.x} y1={prev.y} x2={p.x} y2={p.y} stroke={col} strokeWidth="3.4" strokeLinecap="round" />;
          })}
          <circle cx={pts[0].x} cy={pts[0].y} r="5" fill="white" stroke="var(--ink)" strokeWidth="2" />
          <rect x={pts[pts.length - 1].x - 4.5} y={pts[pts.length - 1].y - 4.5} width="9" height="9" rx="1.5" fill="var(--ink)" stroke="white" strokeWidth="1.4" />
        </svg>
        <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noreferrer"
           style={{ position: "absolute", right: 3, bottom: 2, padding: "1px 3px", background: "rgba(255,255,255,.82)", color: "#334", fontSize: 8.5, textDecoration: "none" }}>
          © OpenStreetMap contributors
        </a>
      </div>
    );
  }

  function ZoneBar({ zoneMinutes, w = 320, h = 22 }) {
    if (!zoneMinutes) return null;
    const total = ZONE_KEYS.reduce((a, k) => a + (zoneMinutes[k] || 0), 0) || 1;
    let x = 0;
    return (
      <svg viewBox={`0 0 ${w} ${h}`} width="100%" height={h} preserveAspectRatio="none" style={{ display: "block" }}>
        {ZONE_KEYS.map((k, i) => {
          const bw = ((zoneMinutes[k] || 0) / total) * w;
          const seg = <rect key={k} x={x} y={0} width={Math.max(0, bw - 1.5)} height={h} rx="2" fill={ZONE_COLORS[i]} opacity="0.92" />;
          x += bw;
          return seg;
        })}
      </svg>
    );
  }

  // ---- aggregate stat card (weekly / monthly / yearly) ----
  function BikeStatCard({ label, s }) {
    return (
      <div>
        <Label style={{ fontSize: 9.5 }}>{label}</Label>
        <div className="num" style={{ fontSize: 20, fontWeight: 600, marginTop: 4 }}>
          {s.distance_km}<span style={{ fontSize: 10, color: "var(--ink-4)", marginLeft: 3 }}>km</span>
        </div>
        <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 3, lineHeight: 1.4 }}>
          {s.rides} ride{s.rides === 1 ? "" : "s"} · avg {s.avg_distance_km} km
        </div>
      </div>
    );
  }

  // ---- ride list row ----
  function RideRow({ r, onOpen }) {
    return (
      <button onClick={onOpen} className="occ-tap"
        style={{ width: "100%", background: "none", border: "none", borderBottom: "1px solid var(--line)",
                 padding: "12px 2px", display: "flex", alignItems: "center", justifyContent: "space-between",
                 textAlign: "left", cursor: "pointer" }}>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--ink)", whiteSpace: "nowrap" }}>{fmtRideDate(r.start_ts).split(" · ")[0]}</div>
          <Label style={{ color: "var(--ink-4)", marginTop: 2, display: "block", whiteSpace: "nowrap" }}>
            {fmtDuration(r.duration_min)} · {r.avg_speed_kmh} km/h avg{r.detector_confidence ? ` · ${r.detector_confidence} confidence` : ""}
          </Label>
        </div>
        <Row gap={12}>
          <div style={{ textAlign: "right" }}>
            <div className="num" style={{ fontSize: 15, fontWeight: 600, whiteSpace: "nowrap" }}>{r.distance_km} km</div>
            <Label style={{ color: "var(--ink-4)", whiteSpace: "nowrap" }}>
              {r.calories_kcal} kcal{r.avg_hr ? ` · ${Math.round(r.avg_hr)} bpm` : ""}
            </Label>
          </div>
          <svg width="13" height="13" viewBox="0 0 13 13" style={{ flexShrink: 0 }}>
            <path d="M4.5 3 L8.5 6.5 L4.5 10" fill="none" stroke="var(--ink-4)" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </Row>
      </button>
    );
  }

  // ---- list screen ----
  function BikeList({ summary, err, onOpen, onRetry }) {
    return (
      <div>
        <ScreenHead sub="cycling · precise phone GPS + Find My confirmation" title="Bike" />
        {err ? (
          <Card><div style={{ padding: "10px 2px", color: "var(--ink-3)", fontSize: 13 }}>
            Couldn't load rides. <button onClick={onRetry} style={{ background: "none", border: "none", color: "var(--ink)", fontWeight: 600, cursor: "pointer", padding: 0 }}>Retry</button>
          </div></Card>
        ) : !summary ? (
          <Card><div style={{ padding: "10px 2px", color: "var(--ink-4)", fontSize: 13 }}>Loading rides…</div></Card>
        ) : (
          <>
            <Card pad={16}>
              <SubLabel>Distance</SubLabel>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10 }}>
                <BikeStatCard label="This week" s={summary.stats.weekly} />
                <BikeStatCard label="This month" s={summary.stats.monthly} />
                <BikeStatCard label="This year" s={summary.stats.yearly} />
              </div>
            </Card>
            <div style={{ marginTop: 12 }}>
              <SubLabel right={`${summary.rides.length} shown`}>Rides</SubLabel>
              <Card pad={16} style={{ paddingTop: 2, paddingBottom: 2 }}>
                {summary.rides.length === 0 ? (
                  <div style={{ padding: "18px 2px", color: "var(--ink-4)", fontSize: 12.5, lineHeight: 1.5 }}>
                    No rides detected yet — OwnTracks motion starts the route and Find My adds confirmation when available.
                  </div>
                ) : summary.rides.map((r) => <RideRow key={r.id} r={r} onOpen={() => onOpen(r.id)} />)}
                <div style={{ height: 2 }} />
              </Card>
            </div>
          </>
        )}
      </div>
    );
  }

  // ---- ride detail screen ----
  function RideDetail({ detail, err, onBack }) {
    return (
      <div>
        <button onClick={onBack} className="occ-tap"
          style={{ display: "flex", alignItems: "center", gap: 6, background: "none", border: "none",
                   color: "var(--ink-2)", padding: "0 0 14px", cursor: "pointer" }}>
          <Icon name="back" size={17} color="var(--ink-2)" />
          <span style={{ fontSize: 13, fontWeight: 600 }}>Rides</span>
        </button>
        {err && <Card><div style={{ padding: "10px 2px", color: "var(--acute-ink)", fontSize: 13 }}>{err}</div></Card>}
        {!detail && !err && <Card><div style={{ padding: "10px 2px", color: "var(--ink-4)", fontSize: 13 }}>Loading ride…</div></Card>}
        {detail && (
          <>
            <Card pad={18}>
              <Row justify="space-between" align="flex-start">
                <div>
                  <Label>{fmtRideDate(detail.start_ts)}</Label>
                  <div style={{ marginTop: 6 }}><Readout value={detail.distance_km} unit="km" size={44} /></div>
                </div>
                <div style={{ textAlign: "right" }}>
                  <div className="num" style={{ fontSize: 18, fontWeight: 600 }}>{detail.calories_kcal}<span style={{ fontSize: 10, color: "var(--ink-4)", marginLeft: 3 }}>kcal</span></div>
                  <Label style={{ marginTop: 4, display: "block" }}>{fmtDuration(detail.duration_min)} · {detail.avg_speed_kmh} km/h</Label>
                </div>
              </Row>
            </Card>

            <Card style={{ marginTop: 12 }}>
              <SubLabel right={detail.avg_hr ? `avg ${Math.round(detail.avg_hr)} · max ${Math.round(detail.max_hr)} bpm` : "no HR coverage"}>Heart-rate zones</SubLabel>
              {detail.zone_minutes ? (
                <>
                  <ZoneBar zoneMinutes={detail.zone_minutes} />
                  <div style={{ display: "flex", gap: 12, marginTop: 10, flexWrap: "wrap" }}>
                    {ZONE_KEYS.map((k, i) => (
                      <Row key={k} gap={5}>
                        <span style={{ width: 9, height: 9, borderRadius: 2, background: ZONE_COLORS[i] }} />
                        <Label style={{ textTransform: "none", letterSpacing: 0, color: "var(--ink-3)" }}>{k.toUpperCase()} {detail.zone_minutes[k] ?? 0}m</Label>
                      </Row>
                    ))}
                  </div>
                </>
              ) : (
                <div style={{ fontSize: 12.5, color: "var(--ink-4)" }}>No heart-rate coverage during this ride.</div>
              )}
            </Card>

            {detail.detector_evidence && (
              <Card style={{ marginTop: 12 }}>
                <SubLabel right={`${detail.detector_confidence || "—"} confidence`}>Trip detection</SubLabel>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.55 }}>
                  {detail.detector_evidence.cycling_points || 0} cycling-motion fixes · {detail.detector_evidence.phone_points || detail.points} precise GPS fixes
                  {detail.detector_evidence.findmy?.confirmed
                    ? ` · Find My confirmed (${detail.detector_evidence.findmy.matches} matches)`
                    : " · Find My pending / not needed"}
                </div>
              </Card>
            )}

            <div style={{ marginTop: 12 }}>
              <SubLabel right={detail.track ? `${detail.points} fixes` : "no track"}>Route</SubLabel>
              <RouteMap track={detail.track} maxHrUsed={detail.max_hr_used} />
              {detail.track && (
                <div style={{ fontSize: 10.5, color: "var(--ink-4)", marginTop: 6, fontFamily: "var(--font-mono)" }}>
                  colored by heart-rate zone · ○ start ■ finish
                </div>
              )}
            </div>
          </>
        )}
      </div>
    );
  }

  // ---- top-level screen: owns its own data (not the shared dashboard snapshot) ----
  function Bike() {
    const [summary, setSummary] = useState(null);
    const [summaryErr, setSummaryErr] = useState(null);
    const [selected, setSelected] = useState(null);
    const [detail, setDetail] = useState(null);
    const [detailErr, setDetailErr] = useState(null);

    const loadSummary = useCallback(async () => {
      setSummaryErr(null);
      try {
        const r = await fetch("/api/bike/summary?limit=30", { credentials: "same-origin" });
        if (r.ok) setSummary(await r.json());
        else setSummaryErr("server error");
      } catch (e) { setSummaryErr(String(e)); }
    }, []);

    useEffect(() => { loadSummary(); }, [loadSummary]);

    useEffect(() => {
      if (selected == null) return;
      setDetail(null); setDetailErr(null);
      (async () => {
        try {
          const r = await fetch(`/api/bike/rides/${selected}`, { credentials: "same-origin" });
          if (r.ok) setDetail(await r.json());
          else setDetailErr("Couldn't load this ride.");
        } catch (e) { setDetailErr(String(e)); }
      })();
    }, [selected]);

    if (selected != null) {
      return <RideDetail detail={detail} err={detailErr} onBack={() => setSelected(null)} />;
    }
    return <BikeList summary={summary} err={summaryErr} onOpen={setSelected} onRetry={loadSummary} />;
  }

  Object.assign(window, { Bike });
})();
