// running/src/shoes.jsx — slice 5 of the module split.
//
// The Shoes tab: footwear rotation/mileage, the per-shoe efficiency
// matrix, aging analysis, attribute preferences, and shopping
// recommendations. All computation lives in the UMD libs
// (FootwearAnalysis / ShoeEfficiency / ShoeSpecs / GaitProfile /
// ShoeCatalog / ShoeRecommendations), referenced via window.*.
// getHRZones comes from the HR-zone lib (window.HRZones —
// lib/hrZones.js, the single zone-rule implementation).

const { C, Card } = window.SharedUI;
const useData = () => (window.__appUseData ? window.__appUseData() : null);
const { getHRZones } = window.HRZones;
const { useState, useMemo } = React;

    function Shoes({ activities, userId, hrZoneConfig, isOwner }) {
      const { activePlan: activePlanSummary, gearList, retireShoe } = useData();
      // Strava-retired shoes are hidden from the rotation list by default;
      // they still count toward the header tally and can be revealed on demand.
      const [showRetired, setShowRetired] = useState(false);
      const gearById = useMemo(
        () => new Map((gearList || []).map(g => [String(g.id), g])),
        [gearList]
      );
      const runs = useMemo(
        () => (activities || []).filter(a => a.type === "Run" || a.type === "VirtualRun"),
        [activities]
      );
      const { maxHR: summaryMaxHR } = useMemo(
        () => getHRZones(hrZoneConfig, activities),
        [hrZoneConfig, activities]
      );

      const fmtPace = (s) => {
        if (!s) return "—";
        const t = Math.round(s);
        return `${Math.floor(t / 60)}:${String(t % 60).padStart(2, "0")}`;
      };
      const fmtDate = (ms) => {
        if (!ms) return "—";
        return new Date(ms).toLocaleDateString("en-US", { month: "short", day: "numeric" });
      };

      return (
        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          {/* Footwear — per-shoe mileage + race-day pick. Strava is the
              SOR for shoes; gear catalog is synced alongside activities
              by the Sync Strava button in Settings. */}
          {(() => {
            const haveGear = Array.isArray(gearList) && gearList.length > 0;

            if (!haveGear) {
              return (
                <Card>
                  <div style={{ fontSize: 13, fontWeight: 600, color: C.textDim, marginBottom: 8 }}>
                    Footwear
                    <span style={{ fontSize: 10, color: C.textMuted, marginLeft: 8, fontWeight: 400 }}>
                      (Strava SOR)
                    </span>
                  </div>
                  <div style={{ fontSize: 11, color: C.textDim, lineHeight: 1.6, padding: "8px 0" }}>
                    No shoes yet. Open any run in the <strong style={{ color: C.cyan }}>Training</strong> log and use the <strong style={{ color: C.cyan }}>Shoe</strong> picker to assign one — or add a new shoe inline. Once a shoe has runs against it, this card shows per-shoe mileage, retirement bars, and a race-day pick scored against your gait profile.
                  </div>
                </Card>
              );
            }

            const shoeMap = window.FootwearAnalysis.analyzeShoes({ gearList, activities: runs, maxHR: summaryMaxHR });
            const shoes = Array.from(shoeMap.values());
            const specsById = new Map();
            for (const s of shoes) {
              const sp = window.ShoeSpecs.resolveSpecs({ name: s.name, brand: s.brand, model: s.model });
              if (sp) specsById.set(s.id, sp);
            }
            const active = shoes.filter(s => !s.retired).sort((a, b) => (b.lastUsedAt || 0) - (a.lastUsedAt || 0));
            const retired = shoes.filter(s => s.retired).sort((a, b) => (b.totalMi || 0) - (a.totalMi || 0));
            if (!active.length && !retired.length) return null;
            const ordered = showRetired ? [...active, ...retired] : [...active];
            const upcomingDist = activePlanSummary?.raceDistance || "marathon";
            const gait = window.GaitProfile.buildGaitProfile({ runs });
            const racePick = window.ShoeSpecs.pickRaceDayShoeWithSpecs({
              shoeMap, raceDistance: upcomingDist, gaitProfile: gait,
            });

            return (
              <Card>
                <div style={{ marginBottom: 12 }}>
                  <div style={{ fontSize: 13, fontWeight: 600, color: C.textDim }}>
                    Footwear
                    <span style={{ fontSize: 10, color: C.textMuted, marginLeft: 8, fontWeight: 400 }}>
                      ({active.length} active · {retired.length} retired · refresh via Settings → Strava)
                    </span>
                  </div>
                </div>

                {racePick.pick ? (
                  <div style={{ padding: "10px 14px", background: C.cyan + "10", border: `1px solid ${C.cyan}40`, borderRadius: 8, marginBottom: 14 }}>
                    <div style={{ fontSize: 10, fontWeight: 700, color: C.cyan, letterSpacing: "0.05em", textTransform: "uppercase", marginBottom: 4 }}>
                      Race-Day Pick · {upcomingDist}
                    </div>
                    <div style={{ fontSize: 14, fontWeight: 700, color: C.text, marginBottom: 4, fontFamily: "'Space Grotesk',sans-serif" }}>
                      {racePick.pick.shoe.name}
                    </div>
                    <div style={{ fontSize: 11, color: C.textMuted, lineHeight: 1.5 }}>
                      {racePick.reasoning}
                    </div>
                  </div>
                ) : (
                  <div style={{ padding: "8px 14px", background: C.bg2, borderRadius: 8, marginBottom: 14, fontSize: 11, color: C.textMuted, fontStyle: "italic" }}>
                    {racePick.reasoning}
                  </div>
                )}

                <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
                  {!ordered.length && (
                    <div style={{ fontSize: 11, color: C.textMuted, fontStyle: "italic", padding: "4px 0" }}>
                      All synced shoes are retired in Strava.
                    </div>
                  )}
                  {ordered.map(s => {
                    const sp = specsById.get(s.id) || null;
                    const retire = window.ShoeSpecs.retirementMiFor(sp);
                    const mi = Math.max(s.totalMi, s.strava.distanceMi);
                    const pct = Math.min(100, (mi / retire) * 100);
                    const milC = mi >= retire ? C.red : mi >= retire * 0.75 ? C.amber : C.green;
                    const catColor = sp?.category === "race" ? C.cyan : sp?.category === "tempo" ? C.amber : sp?.category === "trail" ? "#84cc16" : sp?.category === "max-cushion" ? C.purple || "#a78bfa" : C.green;
                    const g = gearById.get(String(s.id));
                    // Strava is authoritative for its own flag — only
                    // offer the manual shelf when Strava hasn't retired it.
                    const canManual = isOwner && typeof retireShoe === "function" && g && !g.stravaRetired;
                    return (
                      <div key={s.id} style={{ padding: "10px 12px", background: C.bg2, borderRadius: 8, opacity: s.retired ? 0.55 : 1 }}>
                        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
                          <div style={{ flex: "1 1 200px", minWidth: 0 }}>
                            <div style={{ fontSize: 13, fontWeight: 600, color: C.text, display: "flex", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
                              <span>{s.name}</span>
                              {sp?.category && <span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 4, background: catColor + "20", color: catColor, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.04em" }}>{sp.category}</span>}
                              {sp?.plate === "carbon" && <span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 4, background: C.cyan + "20", color: C.cyan, fontWeight: 700 }}>CARBON</span>}
                              {s.primary && <span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 4, background: C.cyan + "20", color: C.cyan, fontWeight: 700 }}>PRIMARY</span>}
                              {s.retired && <span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 4, background: C.textMuted + "20", color: C.textMuted, fontWeight: 700 }}>RETIRED</span>}
                            </div>
                            <div style={{ fontSize: 10, color: C.textMuted, marginTop: 2 }}>
                              {sp?.stackHeightMm != null && <span>{sp.stackHeightMm}mm stack · {sp.dropMm}mm drop · </span>}
                              {s.runCount} run{s.runCount === 1 ? "" : "s"}
                              {s.lastUsedAt && ` · last ${fmtDate(s.lastUsedAt)}`}
                              {s.avgPace && ` · ${fmtPace(s.avgPace)}/mi avg`}
                              {s.avgHR && ` @ ${Math.round(s.avgHR)} bpm`}
                              {s.thresholdEfficiency != null && (
                                <span> · efficiency {s.thresholdEfficiency} ({s.thresholdRunCount} threshold)</span>
                              )}
                            </div>
                          </div>
                          <div style={{ flex: "0 0 auto", textAlign: "right", fontFamily: "'IBM Plex Mono',monospace" }}>
                            <div style={{ fontSize: 14, fontWeight: 700, color: milC }}>{mi.toFixed(1)} <span style={{ fontSize: 10, color: C.textMuted, fontWeight: 400 }}>mi</span></div>
                            {sp?.category && <div style={{ fontSize: 9, color: C.textMuted, marginTop: 1 }}>retire @ {retire}mi</div>}
                            {canManual && (
                              <button
                                onClick={() => retireShoe(s.id, !g.manualRetired)}
                                title={g.manualRetired ? "Move back to the active rotation" : "Shelve this shoe — excluded from rotation, recs and what-works-for-you"}
                                style={{
                                  marginTop: 4, fontSize: 9, color: g.manualRetired ? C.cyan : C.textMuted,
                                  background: "none", border: "none", cursor: "pointer", padding: 0,
                                  fontFamily: "'IBM Plex Mono',monospace", textDecoration: "underline",
                                }}
                              >
                                {g.manualRetired ? "Un-retire" : "Retire"}
                              </button>
                            )}
                          </div>
                        </div>
                        <div style={{ marginTop: 6, height: 4, background: C.bg3, borderRadius: 2, overflow: "hidden" }}>
                          <div style={{ width: `${pct}%`, height: "100%", background: milC, transition: "width 0.3s" }} />
                        </div>
                      </div>
                    );
                  })}
                </div>
                {retired.length > 0 && (
                  <button onClick={() => setShowRetired(v => !v)} style={{
                    marginTop: 8, fontSize: 10, color: C.cyan, background: "none", border: "none", cursor: "pointer",
                    fontFamily: "'IBM Plex Mono',monospace", padding: 0, textDecoration: "underline",
                  }}>
                    {showRetired ? `Hide ${retired.length} retired shoe${retired.length === 1 ? "" : "s"}` : `Show ${retired.length} retired shoe${retired.length === 1 ? "" : "s"}`}
                  </button>
                )}
                <div style={{ marginTop: 8, fontSize: 10, color: C.textMuted, fontStyle: "italic", lineHeight: 1.5 }}>
                  Retired shoes (Strava-flagged or manually shelved via Retire) are hidden by default and excluded from recommendations and what-works-for-you. Bar fills toward category-specific retirement (super shoes 200mi · regular trainers 300mi · trail 500mi). Efficiency = pace-per-HR at threshold-or-faster effort (HR ≥ 85% maxHR); higher is better. Race-day pick weights efficiency by category fit, stack, drop, carbon-plate bonus, and gait-profile fit.
                </div>
              </Card>
            );
          })()}

          {/* Shoe Efficiency by Workout Type — splits each shoe's runs
              into easy / tempo / interval / long, surfaces pace-per-HR
              and run-dynamics medians per cell, and rolls up carbon vs
              non-carbon at the top. Min 3 runs per cell to avoid noise. */}
          {(() => {
            const runs = (activities || []).filter(a => a.type === "Run" || a.type === "VirtualRun");
            const haveGear = Array.isArray(gearList) && gearList.length > 0;
            if (!haveGear || runs.length < 3) return null;
            const result = window.ShoeEfficiency.analyzeShoeEfficiency({
              activities: runs,
              gearList,
              resolveSpecs: window.ShoeSpecs?.resolveSpecs,
              maxHR: summaryMaxHR,
            });
            const shoesWithData = result.shoes.filter(s => s.totalRuns >= window.ShoeEfficiency.MIN_RUNS_PER_CELL);
            if (!shoesWithData.length) return null;

            const fmtPace = (s) => {
              if (s == null) return "—";
              const t = Math.round(s);
              return `${Math.floor(t / 60)}:${String(t % 60).padStart(2, "0")}`;
            };
            const BUCKET_LABELS = { easy: "Easy", tempo: "Tempo", interval: "Interval", long: "Long" };
            const BUCKET_COLORS = { easy: C.green, tempo: C.amber, interval: C.red, long: C.cyan };

            const Cell = ({ cell, accent }) => {
              if (!cell) return (
                <div style={{ flex: "1 1 120px", padding: "8px 10px", background: C.bg2, borderRadius: 6, opacity: 0.4, minWidth: 120 }}>
                  <div style={{ fontSize: 10, color: C.textMuted }}>{"< 3 runs"}</div>
                </div>
              );
              return (
                <div style={{ flex: "1 1 120px", padding: "8px 10px", background: C.bg2, borderRadius: 6, minWidth: 120, borderLeft: `2px solid ${accent}` }}>
                  <div style={{ fontSize: 10, color: C.textMuted, marginBottom: 2 }}>{cell.runCount} runs · {cell.totalMi}mi</div>
                  <div style={{ fontSize: 13, fontWeight: 700, color: C.text }}>
                    {fmtPace(cell.paceSec)}<span style={{ fontSize: 10, color: C.textMuted, fontWeight: 400 }}>/mi</span>
                    {cell.hr && <span style={{ fontSize: 10, color: C.textMuted, fontWeight: 400, marginLeft: 6 }}>@ {cell.hr}</span>}
                  </div>
                  <div style={{ fontSize: 10, color: cell.efficiency ? C.text : C.textMuted, marginTop: 2 }}>
                    eff {cell.efficiency ?? "—"}
                  </div>
                  {(cell.cadenceSpm || cell.verticalRatioPct || cell.groundContactTimeMs) && (
                    <div style={{ fontSize: 9, color: C.textMuted, marginTop: 3, lineHeight: 1.4 }}>
                      {cell.cadenceSpm != null && <span>{cell.cadenceSpm}spm </span>}
                      {cell.groundContactTimeMs != null && <span>· {cell.groundContactTimeMs}ms </span>}
                      {cell.verticalRatioPct != null && <span>· {cell.verticalRatioPct}%</span>}
                    </div>
                  )}
                </div>
              );
            };

            const PlateRow = ({ label, group, color }) => {
              if (!group) return null;
              return (
                <div style={{ marginBottom: 12 }}>
                  <div style={{ fontSize: 11, fontWeight: 700, color, marginBottom: 6, textTransform: "uppercase", letterSpacing: "0.05em" }}>
                    {label} <span style={{ color: C.textMuted, fontWeight: 400 }}>· {group.shoeCount} shoe{group.shoeCount === 1 ? "" : "s"} · {group.totalRuns} runs</span>
                  </div>
                  <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                    {["easy", "tempo", "interval", "long"].map(b => (
                      <div key={b} style={{ flex: "1 1 130px", minWidth: 130 }}>
                        <div style={{ fontSize: 9, color: C.textMuted, marginBottom: 3, textTransform: "uppercase", letterSpacing: "0.04em" }}>{BUCKET_LABELS[b]}</div>
                        <Cell cell={group.byBucket[b]} accent={BUCKET_COLORS[b]} />
                      </div>
                    ))}
                  </div>
                </div>
              );
            };

            return (
              <Card>
                <div style={{ fontSize: 13, fontWeight: 600, color: C.textDim, marginBottom: 4 }}>
                  Shoe Efficiency by Workout Type
                </div>
                <div style={{ fontSize: 10, color: C.textMuted, marginBottom: 12 }}>
                  Pace/HR + dynamics medians per shoe per workout bucket. Cells with fewer than {window.ShoeEfficiency.MIN_RUNS_PER_CELL} runs are dimmed.
                </div>

                {(result.byPlate.carbon || result.byPlate.nonCarbon) && (
                  <div style={{ marginBottom: 16, padding: 12, background: C.bg2, borderRadius: 8 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, color: C.textDim, marginBottom: 8 }}>Carbon vs Non-Carbon</div>
                    <PlateRow label="Carbon-plated" group={result.byPlate.carbon} color={C.cyan} />
                    <PlateRow label="Non-carbon" group={result.byPlate.nonCarbon} color={C.textDim} />
                  </div>
                )}

                {shoesWithData.map(s => (
                  <div key={s.id} style={{ marginBottom: 14, paddingBottom: 14, borderBottom: `1px solid ${C.bg2}` }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8, flexWrap: "wrap" }}>
                      <div style={{ fontSize: 13, fontWeight: 700, color: C.text }}>{s.name}</div>
                      {s.isCarbon && <span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 4, background: C.cyan + "20", color: C.cyan, fontWeight: 700 }}>CARBON</span>}
                      {s.category && <span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 4, background: C.bg2, color: C.textMuted }}>{s.category}</span>}
                      <div style={{ fontSize: 10, color: C.textMuted }}>{s.totalRuns} runs · overall eff {s.overall.efficiency ?? "—"}</div>
                    </div>
                    <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                      {["easy", "tempo", "interval", "long"].map(b => (
                        <div key={b} style={{ flex: "1 1 130px", minWidth: 130 }}>
                          <div style={{ fontSize: 9, color: C.textMuted, marginBottom: 3, textTransform: "uppercase", letterSpacing: "0.04em" }}>{BUCKET_LABELS[b]}</div>
                          <Cell cell={s.byBucket[b]} accent={BUCKET_COLORS[b]} />
                        </div>
                      ))}
                    </div>
                  </div>
                ))}

                <div style={{ fontSize: 10, color: C.textMuted, fontStyle: "italic", lineHeight: 1.5, marginTop: 4 }}>
                  Bucketing: long ≥ 10mi, intervals from name keywords or HR ≥ 87% max, tempo at HR 75-87%, easy below. Efficiency = 1 / (sec_per_mi × bpm) × 1e6 — higher = faster pace at lower HR.
                </div>
              </Card>
            );
          })()}

          {/* What works for you — the runner's own runs rolled up by
              shoe attribute (drop / stack / carbon / brand / style) so
              the strongest evidence-backed preferences are visible and
              feed the recommendations below. */}
          {(() => {
            const haveGear = Array.isArray(gearList) && gearList.length > 0;
            if (!haveGear || runs.length < window.ShoeEfficiency.MIN_RUNS_PER_CELL) return null;
            const prof = window.ShoeEfficiency.analyzeShoePreferences({
              activities: runs, gearList,
              resolveSpecs: window.ShoeSpecs?.resolveSpecs, maxHR: summaryMaxHR,
            });
            const dims = [
              ["Drop", prof.dimensions.drop],
              ["Stack height", prof.dimensions.stack],
              ["Carbon plate", prof.dimensions.plate],
              ["Brand", prof.dimensions.brand],
              ["Style", prof.dimensions.style],
            // A dimension needs ≥2 distinct attribute values to express a
            // preference — a lone group (e.g. every active shoe is "race")
            // has no "rest" to beat and just renders a meaningless single
            // bar. Hide those instead of implying a comparison.
            ].filter(([, d]) => d && Array.isArray(d.groups) && d.groups.length >= 2 && d.groups.some(g => g.cell));
            if (!dims.length) return null;
            const fmtP = (s) => s == null ? "—" : `${Math.floor(Math.round(s) / 60)}:${String(Math.round(s) % 60).padStart(2, "0")}`;
            return (
              <Card>
                <div style={{ fontSize: 13, fontWeight: 600, color: C.textDim, marginBottom: 6 }}>
                  What works for you
                  <span style={{ fontSize: 10, color: C.textMuted, marginLeft: 8, fontWeight: 400 }}>
                    your runs grouped by shoe attribute · pace-per-HR efficiency
                  </span>
                </div>
                {prof.headline ? (
                  <div style={{ padding: "10px 14px", background: C.cyan + "10", border: `1px solid ${C.cyan}40`, borderRadius: 8, marginBottom: 14, fontSize: 12, color: C.text, lineHeight: 1.5 }}>
                    {prof.headline}
                    <div style={{ fontSize: 10, color: C.textMuted, marginTop: 4 }}>These confident preferences are now weighted into the shoe recommendations below.</div>
                  </div>
                ) : (
                  <div style={{ padding: "8px 14px", background: C.bg2, borderRadius: 8, marginBottom: 14, fontSize: 11, color: C.textMuted, fontStyle: "italic" }}>
                    Not enough separation yet to call a clear winner on any single attribute — keep logging runs across your rotation. The per-group numbers below still show the trend.
                  </div>
                )}
                <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
                  {dims.map(([label, dim]) => {
                    const best = dim.preference;
                    const maxEff = Math.max(...dim.groups.map(x => x.cell?.efficiency || 0), 0.0001);
                    return (
                      <div key={label}>
                        <div style={{ fontSize: 11, fontWeight: 700, color: C.textDim, marginBottom: 6, textTransform: "uppercase", letterSpacing: "0.05em" }}>
                          {label}
                          {best?.confident && <span style={{ marginLeft: 8, fontSize: 9, padding: "1px 6px", borderRadius: 4, background: C.cyan + "20", color: C.cyan, fontWeight: 700 }}>{best.bestLabel} +{best.deltaPct}%</span>}
                        </div>
                        <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                          {dim.groups.map(g => {
                            const c = g.cell;
                            const isBest = best && best.confident && g.key === best.bestKey;
                            const eff = c?.efficiency ?? null;
                            return (
                              <div key={g.key} style={{ display: "flex", alignItems: "center", gap: 10, padding: "6px 10px", background: isBest ? C.cyan + "12" : C.bg2, borderRadius: 6, border: isBest ? `1px solid ${C.cyan}40` : "1px solid transparent" }}>
                                <div style={{ flex: "0 0 96px", fontSize: 12, color: C.text, fontWeight: isBest ? 700 : 400, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }} title={g.label}>{g.label}</div>
                                <div style={{ flex: 1, minWidth: 50, height: 6, background: C.bg3, borderRadius: 3, overflow: "hidden" }}>
                                  <div style={{ width: `${eff ? Math.round((eff / maxEff) * 100) : 0}%`, height: "100%", background: isBest ? C.cyan : C.textMuted, transition: "width 0.3s" }} />
                                </div>
                                <div style={{ flex: "0 0 auto", textAlign: "right", fontFamily: "'IBM Plex Mono',monospace", fontSize: 11, color: eff ? C.text : C.textMuted, minWidth: 56 }}>
                                  {eff != null ? `eff ${eff}` : `<${window.ShoeEfficiency.MIN_RUNS_PER_CELL}r`}
                                </div>
                                <div style={{ flex: "0 0 auto", textAlign: "right", fontFamily: "'IBM Plex Mono',monospace", fontSize: 10, color: C.textMuted, minWidth: 104 }}>
                                  {c ? `${c.runCount}r · ${c.totalMi}mi${c.paceSec ? ` · ${fmtP(c.paceSec)}${c.hr ? `@${c.hr}` : ""}` : ""}` : `${g.shoeCount} shoe${g.shoeCount === 1 ? "" : "s"}`}
                                </div>
                              </div>
                            );
                          })}
                        </div>
                      </div>
                    );
                  })}
                </div>
                <div style={{ marginTop: 10, fontSize: 10, color: C.textMuted, fontStyle: "italic", lineHeight: 1.5 }}>
                  Efficiency = pace-per-HR pooled across every run on shoes in the group (higher = faster at lower HR). A preference is only called "confident" (and fed to the recs) when the best group beats the pooled rest by ≥3% with ≥3 runs. Mixed-intensity pooling can confound — read alongside the by-workout table above.
                </div>
              </Card>
            );
          })()}

          {/* Aging & replacement — which active shoes are near
              mileage retirement or drifting down in efficiency vs the
              runner's fitness trend ("on the out"), each paired with a
              targeted, preference-biased catalog replacement. Strava-
              retired shoes are excluded (refresh via Sync Now). */}
          {(() => {
            const haveGear = Array.isArray(gearList) && gearList.length > 0;
            if (!haveGear) return null;
            const aging = window.ShoeEfficiency.analyzeShoeAging({
              activities: runs, gearList,
              resolveSpecs: window.ShoeSpecs?.resolveSpecs,
              retirementMiFor: window.ShoeSpecs?.retirementMiFor,
              maxHR: summaryMaxHR,
            });
            const actionable = aging.shoes.filter(s =>
              s.aging.status !== "ok" || s.drift.status === "watch" || s.drift.status === "declining");
            if (!aging.shoes.length) return null;

            // Targeted replacement: preference-biased catalog rec for the
            // aging shoe's slot (recommend() already penalises owned).
            const gait = window.GaitProfile.buildGaitProfile({ runs });
            const owned = (gearList || []).map(g => window.ShoeSpecs.resolveSpecs({ name: g.name, brand: g.brand, model: g.model })).filter(Boolean);
            const upcomingDist = activePlanSummary?.raceDistance || "marathon";
            const prefs = window.ShoeEfficiency.analyzeShoePreferences({
              activities: runs, gearList, resolveSpecs: window.ShoeSpecs?.resolveSpecs, maxHR: summaryMaxHR,
            }).preferences;
            const recs = window.ShoeRecommendations.recommend({
              gaitProfile: gait, raceDistance: upcomingDist, ownedSpecs: owned, topN: 3, preferences: prefs,
            });
            const replacementFor = (slot, shoeName) => {
              if (!slot) return null;
              const sl = recs.find(r => r.slot === slot);
              if (!sl || !sl.picks.length) return null;
              const p = sl.picks.find(x => `${x.entry.brand} ${x.entry.model}`.toLowerCase() !== (shoeName || "").toLowerCase()) || sl.picks[0];
              return p ? { e: p.entry, reasons: p.reasons } : null;
            };

            const statusChip = (txt, color) => (
              <span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 4, background: color + "20", color, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.04em" }}>{txt}</span>
            );
            const agingColor = (st) => st === "over" ? C.red : st === "aging" ? C.amber : C.green;
            const driftColor = (st) => st === "declining" ? C.red : st === "watch" ? C.amber : C.textMuted;

            return (
              <Card>
                <div style={{ fontSize: 13, fontWeight: 600, color: C.textDim, marginBottom: 4 }}>
                  Aging &amp; replacement
                  <span style={{ fontSize: 10, color: C.textMuted, marginLeft: 8, fontWeight: 400 }}>
                    mileage + efficiency-vs-fitness drift · targeted picks
                  </span>
                </div>
                {!actionable.length ? (
                  <div style={{ padding: "8px 14px", background: C.green + "10", border: `1px solid ${C.green}33`, borderRadius: 8, fontSize: 11, color: C.textDim }}>
                    All active shoes are within their mileage budget and holding efficiency relative to your fitness — nothing to replace yet.
                  </div>
                ) : (
                  <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
                    {actionable.map(s => {
                      const ac = agingColor(s.aging.status);
                      const rep = (s.aging.status !== "ok" || s.drift.status === "declining" || s.drift.status === "watch")
                        ? replacementFor(s.replaceSlot, s.name) : null;
                      return (
                        <div key={s.id} style={{ padding: "10px 12px", background: C.bg2, borderRadius: 8 }}>
                          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
                            <div style={{ fontSize: 13, fontWeight: 600, color: C.text, display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
                              <span>{s.name}</span>
                              {s.aging.status === "over" && statusChip("over", C.red)}
                              {s.aging.status === "aging" && statusChip("aging", C.amber)}
                              {s.drift.status === "declining" && statusChip("on the out", C.red)}
                              {s.drift.status === "watch" && statusChip("watch", C.amber)}
                            </div>
                            <div style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: 12, fontWeight: 700, color: ac }}>
                              {s.aging.mi.toFixed(0)}<span style={{ fontSize: 10, color: C.textMuted, fontWeight: 400 }}>/{s.aging.retirementMi}mi · {s.aging.pct}%</span>
                            </div>
                          </div>
                          <div style={{ marginTop: 6, height: 4, background: C.bg3, borderRadius: 2, overflow: "hidden" }}>
                            <div style={{ width: `${Math.min(100, s.aging.pct)}%`, height: "100%", background: ac, transition: "width 0.3s" }} />
                          </div>
                          {(s.drift.status === "declining" || s.drift.status === "watch") && (
                            <div style={{ fontSize: 10, color: driftColor(s.drift.status), marginTop: 6, lineHeight: 1.5 }}>
                              Efficiency drifting down vs your fitness — {s.drift.slopePer100}/100mi, recent {s.drift.deltaResid > 0 ? "+" : ""}{s.drift.deltaResid} vs early ({s.drift.runs} runs). Looks like wear, not you.
                            </div>
                          )}
                          {rep && (
                            <div style={{ marginTop: 8, padding: "8px 10px", background: C.cyan + "10", border: `1px solid ${C.cyan}33`, borderRadius: 6 }}>
                              <div style={{ fontSize: 9, fontWeight: 700, color: C.cyan, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 3 }}>Targeted replacement</div>
                              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 6, flexWrap: "wrap" }}>
                                <div style={{ fontSize: 12, fontWeight: 700, color: C.text }}>{rep.e.brand} {rep.e.model}</div>
                                <div style={{ fontSize: 10, color: C.textMuted, fontFamily: "'IBM Plex Mono',monospace" }}>{rep.e.stackHeightMm}/{rep.e.dropMm}mm{rep.e.plate === "carbon" ? " · carbon" : ""}</div>
                              </div>
                              {rep.reasons?.length > 0 && (
                                <div style={{ fontSize: 10, color: C.textMuted, marginTop: 3, lineHeight: 1.4 }}>{rep.reasons.slice(0, 2).join(" · ")}</div>
                              )}
                            </div>
                          )}
                        </div>
                      );
                    })}
                  </div>
                )}
                <div style={{ marginTop: 8, fontSize: 10, color: C.textMuted, fontStyle: "italic", lineHeight: 1.5 }}>
                  {aging.fitness.ok
                    ? `Drift = pace-per-HR detrended against your overall efficiency trend (so it isolates shoe wear from fitness changes), regressed on the shoe's accumulated mileage. Conservative: needs ≥6 efficiency runs spanning ≥50mi and both a downward slope and a recent-vs-early drop.`
                    : `Not enough efficiency-bearing runs yet for a fitness baseline — showing mileage status only; the wear signal activates once history builds.`}
                  {aging.retiredShoes.length > 0 && ` Excluded ${aging.retiredShoes.length} Strava-retired shoe${aging.retiredShoes.length === 1 ? "" : "s"} (refresh via Settings → Strava → Sync Now).`}
                </div>
              </Card>
            );
          })()}

          {/* Shoe Recommendations — shopping suggestions from the
              curated catalog scored against gait profile + race
              distance + what's already owned + what's worked for you. */}
          {(() => {
            const gait = window.GaitProfile.buildGaitProfile({ runs });
            const owned = (gearList || []).map(g => window.ShoeSpecs.resolveSpecs({ name: g.name, brand: g.brand, model: g.model })).filter(Boolean);
            const upcomingDist = activePlanSummary?.raceDistance || "marathon";
            const prefProfile = window.ShoeEfficiency.analyzeShoePreferences({
              activities: runs, gearList,
              resolveSpecs: window.ShoeSpecs?.resolveSpecs, maxHR: summaryMaxHR,
            });
            const recs = window.ShoeRecommendations.recommend({
              gaitProfile: gait, raceDistance: upcomingDist, ownedSpecs: owned, topN: 5,
              preferences: prefProfile.preferences,
            });
            const gaps = window.ShoeRecommendations.rotationGaps(owned);
            const slotColor = (slot) => slot === "race" ? C.cyan : slot === "tempo" ? C.amber
              : slot === "cushion" ? (C.purple || "#a78bfa") : slot === "trail" ? "#84cc16" : C.green;

            return (
              <Card>
                <div style={{ fontSize: 13, fontWeight: 600, color: C.textDim, marginBottom: 10 }}>
                  Shoe Recommendations
                  <span style={{ fontSize: 10, color: C.textMuted, marginLeft: 8, fontWeight: 400 }}>
                    Scored against your gait profile · what's worked for you · {upcomingDist} race · catalog of {window.ShoeCatalog.CATALOG.length} models
                  </span>
                </div>

                {gaps.length > 0 && (
                  <div style={{ padding: "10px 14px", background: C.amber + "10", border: `1px solid ${C.amber}40`, borderRadius: 8, marginBottom: 14 }}>
                    <div style={{ fontSize: 10, fontWeight: 700, color: C.amber, letterSpacing: "0.05em", textTransform: "uppercase", marginBottom: 4 }}>
                      Rotation Gaps
                    </div>
                    {gaps.map(g => (
                      <div key={g.slot} style={{ fontSize: 11, color: C.textDim, lineHeight: 1.5 }}>• {g.message}</div>
                    ))}
                  </div>
                )}

                <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 14 }}>
                  {recs.map(slot => (
                    <div key={slot.slot} style={{ padding: "12px 14px", background: C.bg2, borderRadius: 8 }}>
                      <div style={{ fontSize: 11, fontWeight: 700, color: slotColor(slot.slot), letterSpacing: "0.05em", textTransform: "uppercase", marginBottom: 8 }}>
                        {slot.label}
                      </div>
                      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                        {slot.picks.map((p, i) => {
                          const e = p.entry;
                          const isTop = i === 0;
                          return (
                            <div key={`${e.brand}-${e.model}`} style={{
                              padding: "8px 10px", borderRadius: 6,
                              background: isTop ? slotColor(slot.slot) + "15" : "transparent",
                              border: isTop ? `1px solid ${slotColor(slot.slot)}40` : `1px solid ${C.border}40`,
                            }}>
                              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 6 }}>
                                <div style={{ fontSize: 12, fontWeight: 700, color: C.text }}>
                                  {e.brand} {e.model}
                                </div>
                                <div style={{ fontSize: 10, color: C.textMuted, fontFamily: "'IBM Plex Mono',monospace", flexShrink: 0 }}>
                                  {e.stackHeightMm}/{e.dropMm}mm
                                </div>
                              </div>
                              {p.reasons.length > 0 && (
                                <div style={{ fontSize: 10, color: C.textMuted, marginTop: 3, lineHeight: 1.4 }}>
                                  {p.reasons.slice(0, 3).join(" · ")}
                                </div>
                              )}
                              {p.cons && p.cons.length > 0 && (
                                <div style={{ fontSize: 10, color: C.amber, marginTop: 3, lineHeight: 1.4 }}>
                                  Caveat: {p.cons.slice(0, 2).join(" · ")}
                                </div>
                              )}
                              {e.intent && isTop && (
                                <div style={{ fontSize: 10, color: C.textDim, marginTop: 4, fontStyle: "italic", lineHeight: 1.4 }}>
                                  {e.intent}
                                </div>
                              )}
                            </div>
                          );
                        })}
                      </div>
                    </div>
                  ))}
                </div>

                <div style={{ marginTop: 10, fontSize: 10, color: C.textMuted, fontStyle: "italic", lineHeight: 1.5 }}>
                  Recommendations score each catalog model against your gait profile (cadence / strike / efficiency), upcoming race distance, what's already in rotation, and your proven attribute preferences from "What works for you" above (confident drop / stack / carbon / brand edges add weight, and lead the "why this pick" line). Top pick per slot is highlighted; shoes you already own get a strong negative penalty so they don't dominate the list. Five styles (race → trail), top five each; the amber caveat line flags where a pick fights your stride, efficiency, or the race distance.
                </div>
              </Card>
            );
          })()}
        </div>
      );
    }

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