// Preset (science-based) routines — big image cards; detail shows exercises +
// a body map (reusing ExerciseMedia group silhouettes) with worked zones.

const PRT = {
  page:   '#060D18',
  card:   '#0E1A2C',
  panel:  '#0A1422',
  border: 'rgba(255,255,255,0.07)',
  text:   '#E8EDF8',
  sub:    'rgba(232,237,248,0.55)',
  muted:  'rgba(232,237,248,0.55)',
  blue:   '#3B82F6',
  green:  '#22C55E',
};

const PR_LEVELS = [
  { key: 'principiante', label: 'Principiante' },
  { key: 'intermedio',   label: 'Intermedio' },
  { key: 'avanzado',     label: 'Avanzado' },
];
const PR_LVL_ORDER = { principiante: 0, intermedio: 1, avanzado: 2 };

// Default training level from the user's profile (atlas.profile.v1 → experience),
// else intermedio.
function prDefaultLevel() {
  try {
    const p = JSON.parse(localStorage.getItem('atlas.profile.v1') || 'null');
    const map = { beginner: 'principiante', intermediate: 'intermedio', advanced: 'avanzado' };
    return map[p?.experience] || 'intermedio';
  } catch { return 'intermedio'; }
}

// Resolve an exercise to the level-appropriate variant, driven by the existing
// `level`, `pattern`, `variants` and `muscles` data: keeps the same movement
// pattern and primary muscle, picks the entry at the requested level (beginner →
// machine/cable, advanced → free/loaded), and falls back to the nearest level.
function prResolveLevel(baseId, level) {
  if (typeof ExerciseService === 'undefined') return baseId;
  const base = ExerciseService.getById(baseId);
  if (!base || !level) return baseId;
  const norm = m => (m || '').toLowerCase();
  const basePrim = new Set((base.muscles?.primary || []).map(norm));
  const shares = e => (e.muscles?.primary || []).some(m => basePrim.has(norm(m)));
  const variantIds = new Set(base.variants || []);
  const seen = new Set(); const pool = [];
  // Keep the movement TYPE stable: an isolation stays isolation, a compound stays
  // compound (so a cable fly never becomes a barbell press at a higher level).
  [base, ...ExerciseService.getAll({ pattern: base.pattern })].forEach(e => {
    if (e && !seen.has(e.id) && (e.id === base.id || (shares(e) && e.compound === base.compound))) { seen.add(e.id); pool.push(e); }
  });
  const target = PR_LVL_ORDER[level] ?? 1;
  const GUIDED = new Set(['máquina', 'polea']);
  const FREE   = new Set(['barra', 'mancuernas', 'kettlebell', 'bodyweight']);
  const rank = e => {
    const dist = Math.abs((PR_LVL_ORDER[e.level] ?? 1) - target);
    // Beginner → prefer guided (machine/cable); advanced → prefer free weight.
    // Small nudge: only breaks ties between non-base options at the same level.
    let equip = 0;
    if (level === 'principiante' && GUIDED.has(e.equipment)) equip = 5;
    else if (level === 'avanzado' && FREE.has(e.equipment))  equip = 5;
    // The catalog exercise wins any same-distance tie (self bonus > variant bonus)
    // so it's only replaced when another entry is genuinely closer to the level —
    // no drift (e.g. dumbbell → cable) within the same level.
    return dist * 100 - (variantIds.has(e.id) ? 10 : 0) - (e.compound ? 3 : 0) - equip - (e.id === base.id ? 20 : 0);
  };
  const exact = pool.filter(e => e.level === level);
  const pick = (exact.length ? exact : pool).slice().sort((a, b) => rank(a) - rank(b))[0];
  return pick ? pick.id : baseId;
}

// ── Rich template model (single source of truth) ────────────────────────────────
// A template exercise is a MOVEMENT PATTERN, not a fixed exercise: it names a
// main exercise plus valid alternatives, so the Builder can pick the variant that
// fits the user's equipment/level. prTemplateExercise() upgrades any preset row —
// the simple legacy shape {exerciseId, sets, repsMin, repsMax, rir, note} or a
// fully-authored one — to the complete model, deriving whatever the author didn't
// specify from the exercise DB. Explicit fields always win (backward compatible).
//
// Rich shape:
//   { pattern, exerciseId (main), alternatives:[ids], sets, repsMin, repsMax,
//     repsLabel?, rir, rest, tempo?, muscles:{primary,secondary}, level, note? }
function prTemplateExercise(pe) {
  const db = (typeof ExerciseService !== 'undefined' && ExerciseService.getById(pe.exerciseId)) || {};
  const compound = db.compound;
  // Alternatives: explicit list › the DB variants of the main lift › [].
  const alternatives = Array.isArray(pe.alternatives) ? pe.alternatives : (db.variants || []);
  return {
    pattern:      pe.pattern    || db.pattern || null,
    exerciseId:   pe.exerciseId,
    alternatives,
    sets:         pe.sets,
    repsMin:      pe.repsMin,
    repsMax:      pe.repsMax,
    repsLabel:    pe.repsLabel,
    rir:          pe.rir,
    rest:         pe.rest       || (compound ? '120s' : '90s'),
    tempo:        pe.tempo      || null,
    muscles:      pe.muscles    || db.muscles || { primary: [], secondary: [] },
    level:        pe.level      || db.level   || null,
    note:         pe.note,
  };
}

// ── Shared-format conversion (single source of truth Coach → Builder → Player) ──
// Normalizes the template row, resolves the level-appropriate variant, and emits
// the executable shape the Builder/Player consume — now carrying the pattern,
// alternatives, rest, tempo and level so a session stays a rich, re-resolvable
// structure end to end.
function prExToShared(pe, level) {
  const tmpl = prTemplateExercise(pe);
  const rid  = prResolveLevel(tmpl.exerciseId, level);
  const db   = (typeof ExerciseService !== 'undefined' && ExerciseService.getById(rid)) || {};
  const repsRange = tmpl.repsLabel || `${tmpl.repsMin}-${tmpl.repsMax}`;
  const n = Math.max(1, tmpl.sets || 1);
  return {
    id: rid,
    name: db.name || rid,
    group: db.group || db.category || '',
    muscles: db.muscles || tmpl.muscles || { primary: [], secondary: [] },
    pattern: db.pattern || tmpl.pattern || null,
    repsRange,
    rir: tmpl.rir,
    rest: tmpl.rest,
    tempo: tmpl.tempo || null,
    level: db.level || tmpl.level || null,
    // Template provenance — lets the Builder offer the alternatives / re-resolve.
    template: { exerciseId: tmpl.exerciseId, pattern: tmpl.pattern, alternatives: tmpl.alternatives },
    setsCount: n,
    sets: Array.from({ length: n }, () => ({ kg: '', reps: tmpl.repsMin ? String(tmpl.repsMin) : '' })),
    _swapped: rid !== tmpl.exerciseId,
  };
}
function prToWorkout(preset, level) { return (preset.exercises || []).map(pe => prExToShared(pe, level)); }

// ── Multi-day (split) normalization ─────────────────────────────────────────────
// A routine either declares `days: [{ name, exercises }]` (split) or a single
// flat `exercises` array (legacy). prDays() always returns a days array so the UI
// has one code path. Single-day routines behave exactly as before.
function prDays(preset) {
  if (Array.isArray(preset?.days) && preset.days.length) return preset.days;
  return [{ name: preset?.subtitle || preset?.name || 'Sesión', exercises: preset?.exercises || [] }];
}
function prIsMultiDay(preset) { return Array.isArray(preset?.days) && preset.days.length > 1; }
function prDayToWorkout(day, level) { return (day?.exercises || []).map(pe => prExToShared(pe, level)); }
// All sessions share the same exercise selection (e.g. Full Body) → the body map
// is the same every day; otherwise (Upper/Lower) each day has its own focus.
function prDaysIdentical(preset) {
  const ds = prDays(preset);
  if (ds.length < 2) return true;
  const first = JSON.stringify(ds[0].exercises || []);
  return ds.every(d => JSON.stringify(d.exercises || []) === first);
}

// ── Small exercise thumbnail (reuses ExerciseImages, self-hides on miss) ────────
function PrtThumb({ id, name }) {
  const [ok, setOk] = React.useState(true);
  const url = (typeof ExerciseImages !== 'undefined') ? ExerciseImages.urlFor(id) : null;
  if (!url || !ok) return (
    <div style={{ width:44, height:44, borderRadius:9, flexShrink:0, position:'relative', overflow:'hidden',
      background:'linear-gradient(150deg, #16233B 0%, #0F1A2E 100%)', border:`1px solid ${PRT.border}`,
      display:'flex', alignItems:'center', justifyContent:'center' }}>
      <div style={{ position:'absolute', inset:0, backgroundImage:'radial-gradient(rgba(255,255,255,0.06) 1px, transparent 1px)', backgroundSize:'8px 8px' }} />
      <span style={{ width:14, height:14, borderRadius:'50%', border:'1.5px solid rgba(147,197,253,0.5)', position:'relative' }} />
    </div>
  );
  return (
    <div style={{ width:44, height:44, borderRadius:9, overflow:'hidden', flexShrink:0, background:'#0A1422' }}>
      <img src={url} alt={name} loading="lazy" onError={() => setOk(false)}
        style={{ width:'100%', height:'100%', objectFit:'cover', display:'block' }} />
    </div>
  );
}

// ── Hero graphic: free-license photo over the accent gradient; on error falls
//    back to the reused group silhouette (self-contained, offline-safe). ────────
function PrtHero({ preset, height = 150 }) {
  const [imgOk, setImgOk] = React.useState(true);
  const Sil = (typeof ExerciseMedia !== 'undefined' && ExerciseMedia.SVGS[preset.heroGroup]) || null;
  const a = preset.accent || { from:'#0f2744', to:'#1d5bbf' };
  const showPhoto = preset.image && imgOk;
  return (
    <div style={{ position:'relative', height, overflow:'hidden',
      background:`linear-gradient(135deg, ${a.from}, ${a.to})` }}>
      {showPhoto ? (
        <img src={preset.image} alt={preset.name} loading="lazy" onError={() => setImgOk(false)}
          style={{ position:'absolute', inset:0, width:'100%', height:'100%', objectFit:'cover', display:'block' }} />
      ) : (
        <div style={{ position:'absolute', inset:0, opacity:0.9 }}>{Sil ? <Sil /> : null}</div>
      )}
      <div style={{ position:'absolute', inset:0, background:'linear-gradient(to top, rgba(6,13,24,0.78), rgba(6,13,24,0.15) 55%, transparent 80%)' }} />
    </div>
  );
}

// ── Body map: anatomía real (window.MUSCLE_MAP vía AtlasBodyHeat), frente y
//    espalda, solo lectura. Primarios en azul Atlas; secundarios en el mismo azul
//    más tenue; el resto, la silueta base. (Antes apilaba las miniaturas
//    decorativas de ExerciseMedia.SVGS, que no son para superponerse: mancha.)
function PrtBodyMap({ map, exercises }) {
  const mobile = (typeof useIsMobile === 'function') ? useIsMobile() : false;
  const highlight = (typeof mvHighlight === 'function') ? mvHighlight(exercises || [], map) : {};
  const labels = [...((map?.primary?.labels) || []), ...((map?.secondary?.labels) || [])];
  const aria = labels.length ? `Músculos trabajados: ${labels.join(', ')}` : 'Músculos trabajados';
  const hasBody = (typeof AtlasBodyHeat === 'function') && (typeof window.MUSCLE_MAP !== 'undefined');
  return (
    <div style={{ display:'flex', gap:16, alignItems:'center', flexWrap:'wrap' }}>
      {/* Mapa anatómico real: frente + espalda, altura fija, sin deformar */}
      <figure role="img" aria-label={aria}
        style={{ margin:0, flexShrink:0, display:'flex', gap:4, justifyContent:'center', alignItems:'center',
          height: mobile ? 180 : 200, borderRadius:14, background:'linear-gradient(160deg, #0A1626, #0E2038)', border:`1px solid ${PRT.border}`, padding:'8px 14px' }}>
        {hasBody && ['front', 'back'].map(v => (
          <AtlasBodyHeat key={v} view={v} highlight={highlight} interactive={false} height={mobile ? 162 : 182} />
        ))}
      </figure>
      {/* Legend — primary vs secondary */}
      <div style={{ flex:1, minWidth:150 }}>
        <div style={{ marginBottom:10 }}>
          <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:4 }}>
            <span style={{ width:10, height:10, borderRadius:3, background:PRT.blue, flexShrink:0, boxShadow:`0 0 8px ${PRT.blue}` }} />
            <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:800, letterSpacing:1, color:PRT.blue }}>PRIMARIO</span>
          </div>
          {(map?.primary?.labels || []).map((l,i) => (
            <div key={i} style={{ fontFamily:'Inter,system-ui', fontSize:12.5, fontWeight:700, color:PRT.text, lineHeight:1.4 }}>{l}</div>
          ))}
        </div>
        <div>
          <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:4 }}>
            <span style={{ width:10, height:10, borderRadius:3, background:'rgba(59,130,246,0.40)', flexShrink:0 }} />
            <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:800, letterSpacing:1, color:PRT.sub }}>SECUNDARIO</span>
          </div>
          <div style={{ fontFamily:'Inter,system-ui', fontSize:12.5, color:PRT.sub, lineHeight:1.5 }}>
            {(map?.secondary?.labels || []).join(' · ')}
          </div>
        </div>
      </div>
    </div>
  );
}

function PrtTag({ children }) {
  return (
    <span style={{ fontFamily:'Inter,system-ui', fontSize:10.5, fontWeight:700, color:PRT.sub,
      background:'rgba(255,255,255,0.05)', border:`1px solid ${PRT.border}`, borderRadius:999, padding:'4px 10px', whiteSpace:'nowrap' }}>
      {children}
    </span>
  );
}

// ── Big card (carousel item / grid cell) ──────────────────────────────────────
function PrtCard({ preset, onOpen, isMobile, grid }) {
  const days = prDays(preset);
  const multi = prIsMultiDay(preset);
  const exPerDay = (days[0]?.exercises || []).length;
  // Grid mode: the card fills its column. Carousel mode: fixed-ish width so the
  // next card peeks and it snaps into place.
  const sizing = grid
    ? { width:'100%' }
    : { flex:'0 0 auto', width: isMobile ? 'min(84vw, 340px)' : 320, scrollSnapAlign:'start' };
  return (
    <button onClick={onOpen} style={{ display:'block', textAlign:'left', cursor:'pointer', padding:0,
      ...sizing,
      borderRadius:18, overflow:'hidden', border:`1px solid ${PRT.border}`, background:PRT.card, boxShadow:'0 10px 30px -14px rgba(0,0,0,0.6)' }}>
      <div style={{ position:'relative' }}>
        <PrtHero preset={preset} height={150} />
        {multi && (
          <span style={{ position:'absolute', top:10, left:10, fontFamily:'ui-monospace,Menlo,monospace', fontSize:9.5, fontWeight:800,
            letterSpacing:1, color:'#fff', background:'rgba(42,111,219,0.92)', borderRadius:999, padding:'4px 9px', boxShadow:'0 2px 8px rgba(0,0,0,0.35)' }}>
            {days.length} DÍAS
          </span>
        )}
      </div>
      <div style={{ padding:'14px 16px 16px' }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:10 }}>
          <div style={{ fontFamily:'Inter,system-ui', fontSize:20, fontWeight:800, color:PRT.text, letterSpacing:-0.5 }}>{preset.name}</div>
          <span style={{ fontFamily:'Inter,system-ui', fontSize:12, fontWeight:800, color:PRT.blue, flexShrink:0 }}>Ver rutina →</span>
        </div>
        {preset.subtitle && <div style={{ fontFamily:'Inter,system-ui', fontSize:12.5, color:PRT.sub, marginTop:3 }}>{preset.subtitle}</div>}
        <div style={{ display:'flex', gap:6, flexWrap:'wrap', marginTop:12 }}>
          <PrtTag>{preset.group}</PrtTag>
          {preset.frequency && <PrtTag>{preset.frequency}</PrtTag>}
          {multi
            ? <PrtTag>{days.length} días · {exPerDay} ej./día</PrtTag>
            : <PrtTag>{(preset.exercises || []).length} ejercicios</PrtTag>}
        </div>
      </div>
    </button>
  );
}

// ── Shared routine-detail view ──────────────────────────────────────────────────
// Presentational detail used by BOTH preset routines and saved "Mis rutinas", so
// the two look identical: hero/header, chips, day navigation, body map (worked
// zones) and the exercise list (image · sets × reps · RIR · rest). Consumes a
// normalized model; the caller supplies the action buttons and any extra chrome.
//   days: [{ name, muscleMap, exercises:[{ id, name, muscle, sets, reps, rir, rest, note?, swapped? }] }]
function RtDetail({
  title, subtitle, chips, hero, titleAccessory, days, renderActions, footerNote,
  afterActions, coachNote, showLevel, level, onLevel, onBack, backLabel,
  bodyMapNote, listSuffix,
}) {
  const list = (Array.isArray(days) && days.length) ? days : [{ name:'Sesión', exercises:[], muscleMap:null }];
  const multi = list.length > 1;
  const { navigate } = (typeof useRoute === 'function') ? useRoute() : { navigate: () => {} };
  const openArticle = (id) => { try { localStorage.setItem('atlas.aula.pending.v1', id); } catch (e) {} navigate('/aula'); };
  // Distribución muscular de TODA la rutina (todos los días).
  const allRows = list.flatMap(d => (d.exercises || [])).map(r => ({ muscles: r.muscles || { primary: r.muscle ? [r.muscle] : [], secondary: [] }, sets: Number(r.sets) || 0 }));
  const [dayIdx, setDayIdx] = React.useState(0);
  const idx = Math.min(dayIdx, list.length - 1);
  const day = list[idx] || list[0];
  const dayEx = day.exercises || [];
  // Swipe between days (prev/next) with a direction hint for the slide animation.
  const [dayDir, setDayDir] = React.useState(1);
  const changeDay = (next) => { if (next === idx) return; setDayDir(next >= idx ? 1 : -1); setDayIdx(next); };
  const daySwipe = useSwipeNav({ index: idx, count: list.length, onChange: changeDay });
  return (
    <div>
      <button onClick={onBack} style={{ background:'none', border:`1px solid ${PRT.border}`, borderRadius:9, color:PRT.sub,
        padding:'7px 13px', cursor:'pointer', fontFamily:'Inter,system-ui', fontSize:12, fontWeight:700, marginBottom:16 }}>
        {backLabel || '← Volver'}
      </button>

      <div style={{ borderRadius:18, overflow:'hidden', border:`1px solid ${PRT.border}`, background:PRT.card, marginBottom:16 }}>
        {hero && <PrtHero preset={hero} height={160} />}
        <div style={{ padding:'16px 18px' }}>
          <div style={{ display:'flex', alignItems:'flex-start', justifyContent:'space-between', gap:12 }}>
            <div style={{ flex:1, minWidth:0 }}>
              {typeof title === 'string'
                ? <div style={{ fontFamily:'Inter,system-ui', fontSize:24, fontWeight:800, color:PRT.text, letterSpacing:-0.6, overflow:'hidden', textOverflow:'ellipsis' }}>{title}</div>
                : title}
            </div>
            {titleAccessory}
          </div>
          {subtitle && <div style={{ fontFamily:'Inter,system-ui', fontSize:13, color:PRT.sub, marginTop:3 }}>{subtitle}</div>}
          {chips && chips.length > 0 && (
            <div style={{ display:'flex', gap:6, flexWrap:'wrap', marginTop:12 }}>
              {chips.map((c, i) => <PrtTag key={i}>{c}</PrtTag>)}
            </div>
          )}

          {/* Day tabs (split routines) — navigate between sessions. ≤4 days fill
              the width; 5+ scroll horizontally so any split fits on mobile. */}
          {multi && (
            <div role="tablist" aria-label="Días de la rutina" style={{ display:'flex', gap:6, marginTop:14, background:'rgba(255,255,255,0.04)', border:`1px solid ${PRT.border}`, borderRadius:11, padding:4, overflowX:'auto', scrollbarWidth:'none' }}>
              {list.map((d, i) => {
                const active = i === idx;
                const scroll = list.length > 4;
                return (
                  <button key={i} role="tab" aria-selected={active} onClick={() => changeDay(i)}
                    style={{ flex: scroll ? '0 0 auto' : 1, minWidth: scroll ? 56 : 0, whiteSpace:'nowrap', minHeight:38, borderRadius:8, border:'none', cursor:'pointer',
                      background: active ? '#2563EB' : 'transparent', color: active ? '#fff' : PRT.sub,
                      fontFamily:'Inter,system-ui', fontSize:12.5, fontWeight:800, letterSpacing:-0.2, transition:'all .15s' }}>
                    Día {i + 1}
                  </button>
                );
              })}
            </div>
          )}

          {/* Actions — per session (the selected day), supplied by the caller */}
          {renderActions && <div style={{ marginTop:14 }}>{renderActions(idx)}</div>}
          {footerNote && (
            <div style={{ fontFamily:'Inter,system-ui', fontSize:10.5, color:PRT.muted, marginTop:8, textAlign:'center' }}>{footerNote}</div>
          )}
          {afterActions}
        </div>
      </div>

      {/* Programming note (optional) */}
      {coachNote && (
        <div style={{ borderRadius:16, border:'1px solid rgba(42,111,219,0.22)', background:'rgba(42,111,219,0.06)', padding:'14px 16px', marginBottom:16, display:'flex', gap:11 }}>
          <span style={{ flexShrink:0, width:22, height:22, borderRadius:7, background:'#2563EB', color:'#fff', display:'inline-flex', alignItems:'center', justifyContent:'center', fontSize:12, fontWeight:800 }}>i</span>
          <div>
            <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9.5, fontWeight:800, letterSpacing:1, color:PRT.blue, marginBottom:4 }}>NOTA DE PROGRAMACIÓN</div>
            <p style={{ fontFamily:'Inter,system-ui', fontSize:13, color:PRT.text, lineHeight:1.55, margin:0 }}>{coachNote}</p>
          </div>
        </div>
      )}

      {/* Level selector (preset only) — swaps each movement for its level variant */}
      {showLevel && (
        <div style={{ borderRadius:16, border:`1px solid ${PRT.border}`, background:PRT.panel, padding:'14px 16px', marginBottom:16 }}>
          <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:10, marginBottom:10 }}>
            <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, fontWeight:700, color:PRT.sub, letterSpacing:1.2 }}>NIVEL</span>
            <span style={{ fontFamily:'Inter,system-ui', fontSize:10, color:PRT.muted }}>ajusta las variantes</span>
          </div>
          <div style={{ display:'flex', gap:6, background:'rgba(255,255,255,0.04)', border:`1px solid ${PRT.border}`, borderRadius:11, padding:4 }}>
            {PR_LEVELS.map(l => {
              const active = l.key === level;
              return (
                <button key={l.key} onClick={() => onLevel(l.key)}
                  aria-pressed={active} aria-label={`Nivel ${l.label}`}
                  style={{ flex:1, minHeight:40, borderRadius:8, border:'none', cursor:'pointer',
                    background: active ? '#2563EB' : 'transparent', color: active ? '#fff' : PRT.sub,
                    fontFamily:'Inter,system-ui', fontSize:12.5, fontWeight:800, letterSpacing:-0.2, transition:'all .15s' }}>
                  {l.label}
                </button>
              );
            })}
          </div>
        </div>
      )}

      {/* Day-dependent content (body map + exercise list). On multi-day routines a
          horizontal swipe moves to the previous/next day; the muscle map ignores
          swipes so it never hijacks the gesture. */}
      <div {...(multi ? daySwipe : {})}>
      <div key={idx} className={multi ? (dayDir === 1 ? 'atlas-tab-in-next' : 'atlas-tab-in-prev') : undefined}>

      {/* Body map */}
      <div data-swipe-ignore style={{ borderRadius:16, border:`1px solid ${PRT.border}`, background:PRT.panel, padding:'16px 18px', marginBottom:16 }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:8, marginBottom:14 }}>
          <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, fontWeight:700, color:PRT.sub, letterSpacing:1.2 }}>ZONAS TRABAJADAS</span>
          {multi && bodyMapNote && <span style={{ fontFamily:'Inter,system-ui', fontSize:10, color:PRT.muted, textAlign:'right' }}>
            {bodyMapNote(day, idx)}
          </span>}
        </div>
        <PrtBodyMap map={day.muscleMap} exercises={day.exercises} />

        {/* Distribución muscular de la rutina (todos los días) */}
        {typeof MuscleDistribution === 'function' && (
          <div style={{ borderTop:`1px solid ${PRT.border}`, marginTop:14, paddingTop:4 }}>
            <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, fontWeight:700, color:PRT.sub, letterSpacing:1.2 }}>DISTRIBUCIÓN</span>
            <MuscleDistribution exercises={allRows} onOpenArticle={openArticle} />
          </div>
        )}
      </div>

      {/* Exercise list — the selected day */}
      <div style={{ borderRadius:16, border:`1px solid ${PRT.border}`, background:PRT.panel, padding:'8px 14px 14px' }}>
        <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, fontWeight:700, color:PRT.sub, letterSpacing:1.2, margin:'10px 4px 8px' }}>
          {multi ? `${(day.name || 'Sesión').toUpperCase()}` : 'EJERCICIOS'}{listSuffix ? ` · ${listSuffix}` : ''}
        </div>
        {dayEx.length === 0 && (
          <div style={{ fontFamily:'Inter,system-ui', fontSize:12, color:PRT.muted, padding:'8px 4px' }}>Esta sesión no tiene ejercicios.</div>
        )}
        <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
          {dayEx.map((ex, i) => (
            <div key={i} style={{ display:'flex', alignItems:'center', gap:12, padding:'8px 10px', borderRadius:12, background:'rgba(255,255,255,0.03)', border:`1px solid ${PRT.border}` }}>
              <PrtThumb id={ex.id} name={ex.name} />
              <div style={{ flex:1, minWidth:0 }}>
                <div style={{ display:'flex', alignItems:'center', gap:6 }}>
                  <span style={{ fontFamily:'Inter,system-ui', fontSize:13.5, fontWeight:700, color:PRT.text, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{ex.name}</span>
                  {ex.swapped && <span title="Variante por nivel" style={{ flexShrink:0, fontFamily:'Inter,system-ui', fontSize:9, fontWeight:800, color:'#93C5FD', background:'rgba(59,130,246,0.14)', borderRadius:999, padding:'1px 6px' }}>↔</span>}
                </div>
                <div style={{ fontFamily:'Inter,system-ui', fontSize:11, color:PRT.muted, marginTop:2 }}>
                  {prtMuscleLine(ex.muscle, ex.note)}
                </div>
              </div>
              <div style={{ textAlign:'right', flexShrink:0 }}>
                <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:13, fontWeight:800, color:PRT.text }}>{ex.sets} × {ex.reps}</div>
                <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, color:PRT.blue, fontWeight:700, marginTop:1 }}>
                  {ex.isometric ? (ex.rest || '') : `RIR ${ex.rir}${ex.rest ? ` · ${ex.rest}` : ''}`}
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
      </div>
      </div>
    </div>
  );
}

// Map a preset "pe" row (level-resolved) to the shared exercise-row model.
function prExToRow(pe, level) {
  const shared = prExToShared(pe, level);
  const isometric = /isom[eé]trico/i.test(pe.repsLabel || '') || shared.unit === 'time';
  return {
    id: shared.id, name: shared.name,
    muscle: (shared.muscles?.primary?.[0]) || '',
    muscles: shared.muscles || { primary: [], secondary: [] },
    // Isométricos: muestran duración (p. ej. "20–40 s") en vez de "Isométrico".
    sets: pe.sets, reps: isometric ? (pe.secLabel || '20–40 s') : (pe.repsLabel || `${pe.repsMin}-${pe.repsMax}`),
    rir: pe.rir, rest: shared.rest, note: pe.note, swapped: shared._swapped, isometric,
  };
}

// Línea "músculo · nota" sin repetir el nombre del músculo (evita "Core · core"):
// si la nota empieza por el músculo, se elimina ese prefijo.
function prtMuscleLine(muscle, note) {
  const m = (muscle || '').trim();
  let n = (note || '').trim();
  if (m && n && n.toLowerCase().startsWith(m.toLowerCase())) {
    n = n.slice(m.length).replace(/^\s*[·:\-–]\s*/, '').trim();
  }
  return m + (n ? ` · ${n}` : '');
}

// ── Preset detail view — thin wrapper over the shared RtDetail ──────────────────
function PrtDetail({ preset, level, onLevel, onBack, onTrain, onLoadBuilder }) {
  const days = prDays(preset);
  const multi = prIsMultiDay(preset);
  const identical = prDaysIdentical(preset);
  const d0 = days[0]?.exercises || [];
  const totalSets0 = d0.reduce((t, e) => t + (e.sets || 0), 0);
  const chips = [
    preset.group, preset.objective, preset.frequency,
    multi ? `${days.length} días · ${d0.length} ej./día` : `${d0.length} ejercicios · ${totalSets0} series`,
  ].filter(Boolean);
  const modelDays = days.map(d => ({
    name: d.name,
    muscleMap: d.muscleMap || preset.muscleMap,
    exercises: (d.exercises || []).map(pe => prExToRow(pe, level)),
  }));
  return (
    <RtDetail
      title={preset.name}
      subtitle={preset.subtitle}
      chips={chips}
      hero={preset}
      coachNote={preset.coachNote}
      days={modelDays}
      showLevel level={level} onLevel={onLevel}
      listSuffix={PR_LEVELS.find(l => l.key === level)?.label || ''}
      bodyMapNote={(day) => identical ? `cuerpo completo · las ${days.length} sesiones son iguales` : day.name}
      onBack={onBack} backLabel="← Rutinas predeterminadas"
      footerNote={multi ? 'Cada día es una sesión — entrena o edita la que toque.' : 'Editas tu copia — la plantilla original no se modifica.'}
      renderActions={(dayIdx) => (
        <div style={{ display:'flex', gap:10 }}>
          <button onClick={() => onTrain(dayIdx)} style={{ flex:1, minHeight:48, borderRadius:12, border:'none', cursor:'pointer', background:PRT.green, color:'#fff',
            fontFamily:'Inter,system-ui', fontSize:14, fontWeight:800, letterSpacing:-0.2, boxShadow:'0 4px 18px -4px rgba(34,197,94,0.45)' }}>
            {multi ? `▶ Entrenar Día ${dayIdx + 1}` : '▶ Entrenar ahora'}
          </button>
          <button onClick={() => onLoadBuilder(dayIdx)} style={{ flex:1, minHeight:48, borderRadius:12, cursor:'pointer', background:'rgba(255,255,255,0.04)', color:PRT.text,
            border:`1px solid ${PRT.border}`, fontFamily:'Inter,system-ui', fontSize:14, fontWeight:700 }}>
            Cargar en Builder
          </button>
        </div>
      )}
    />
  );
}

// ── Section ─────────────────────────────────────────────────────────────────
// ── Carousel with desktop arrows + "Ver todas" grid ──────────────────────────
// A horizontal scroller can't be moved with a mouse wheel on desktop (no swipe,
// wheel only scrolls the page vertically), so the right-hand cards are stranded.
// Fine-pointer devices get ◀ ▶ arrows (hidden at the ends) that scroll one card
// smoothly; Shift+wheel and click-drag also scroll it horizontally without
// breaking the page's vertical scroll. "Ver todas" flips to a vertical grid.
function PrtCarousel({ presets, onOpen, isMobile }) {
  const scrollRef = React.useRef(null);
  const [showAll, setShowAll] = React.useState(false);
  const [canLeft, setCanLeft]   = React.useState(false);
  const [canRight, setCanRight] = React.useState(false);
  // Arrows only where there's a fine pointer and no touch swipe (desktop mouse).
  const finePointer = React.useMemo(() => {
    try { return window.matchMedia('(hover: hover) and (pointer: fine)').matches; } catch { return false; }
  }, []);
  const showArrows = finePointer && !isMobile;

  const update = React.useCallback(() => {
    const el = scrollRef.current;
    if (!el) return;
    setCanLeft(el.scrollLeft > 4);
    setCanRight(el.scrollLeft < el.scrollWidth - el.clientWidth - 4);
  }, []);

  React.useEffect(() => {
    if (showAll) return;
    const el = scrollRef.current;
    if (!el) return;
    update();
    el.addEventListener('scroll', update, { passive: true });
    let ro;
    try { ro = new ResizeObserver(update); ro.observe(el); } catch {}
    window.addEventListener('resize', update);
    return () => { el.removeEventListener('scroll', update); if (ro) ro.disconnect(); window.removeEventListener('resize', update); };
  }, [showAll, update, presets.length]);

  const step = () => {
    const el = scrollRef.current;
    const first = el && el.firstElementChild;
    return first ? first.getBoundingClientRect().width + 16 : (el ? el.clientWidth * 0.8 : 320);
  };
  const scrollBy = (dir) => { const el = scrollRef.current; if (el) el.scrollBy({ left: dir * step(), behavior: 'smooth' }); };

  // Shift+wheel → horizontal scroll (leave plain wheel for the page's vertical scroll).
  const onWheel = (e) => {
    if (!showArrows) return;
    if (e.shiftKey && e.deltaY !== 0) { scrollRef.current.scrollLeft += e.deltaY; e.preventDefault(); }
  };
  // Click-drag to scroll (desktop). A move threshold suppresses the card click.
  const drag = React.useRef({ active:false, startX:0, startScroll:0, moved:false });
  const suppressClick = React.useRef(false);
  const onPointerDown = (e) => {
    if (!showArrows || e.button !== 0) return;
    drag.current = { active:true, startX:e.clientX, startScroll:scrollRef.current.scrollLeft, moved:false };
  };
  const onPointerMove = (e) => {
    const d = drag.current; if (!d.active) return;
    const dx = e.clientX - d.startX;
    if (Math.abs(dx) > 5) d.moved = true;
    scrollRef.current.scrollLeft = d.startScroll - dx;
  };
  const endDrag = () => { if (drag.current.moved) suppressClick.current = true; drag.current.active = false; };
  const handleOpen = (id) => { if (suppressClick.current) { suppressClick.current = false; return; } onOpen(id); };

  const toggleBtn = (
    <button onClick={() => setShowAll(v => !v)} style={{ background:'none', border:`1px solid ${PRT.border}`, borderRadius:9, color:PRT.sub,
      padding:'6px 12px', cursor:'pointer', fontFamily:'Inter,system-ui', fontSize:12, fontWeight:700 }}>
      {showAll ? '↔ Ver carrusel' : '▦ Ver todas'}
    </button>
  );

  if (showAll) {
    return (
      <>
        <div style={{ display:'flex', justifyContent:'flex-end', marginBottom:12 }}>{toggleBtn}</div>
        <div style={{ display:'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(auto-fill, minmax(280px, 1fr))', gap:16 }}>
          {presets.map(p => <PrtCard key={p.id} preset={p} grid onOpen={() => onOpen(p.id)} isMobile={isMobile} />)}
        </div>
      </>
    );
  }

  const arrowBtn = (dir, visible) => (
    <button onClick={() => scrollBy(dir)} aria-label={dir < 0 ? 'Anterior' : 'Siguiente'} className="wp-press"
      style={{ position:'absolute', top:'50%', [dir < 0 ? 'left' : 'right']: -6, transform:'translateY(-50%)',
        width:40, height:40, borderRadius:'50%', border:`1px solid ${PRT.border}`, cursor:'pointer', zIndex:3,
        background:'rgba(6,13,24,0.92)', backdropFilter:'blur(6px)', color:PRT.text, fontSize:17, fontWeight:800,
        display: visible ? 'flex' : 'none', alignItems:'center', justifyContent:'center',
        boxShadow:'0 6px 20px -6px rgba(0,0,0,0.6)' }}>
      {dir < 0 ? '‹' : '›'}
    </button>
  );

  return (
    <>
      {showArrows && (
        <div style={{ display:'flex', justifyContent:'flex-end', marginBottom:10 }}>{toggleBtn}</div>
      )}
      <div style={{ position:'relative' }}>
        {showArrows && arrowBtn(-1, canLeft)}
        {showArrows && arrowBtn(1, canRight)}
        <div ref={scrollRef} className="pr-carousel"
          onWheel={onWheel} onPointerDown={onPointerDown} onPointerMove={onPointerMove} onPointerUp={endDrag} onPointerLeave={endDrag}
          style={{ display:'flex', gap:16, overflowX:'auto', overflowY:'hidden',
            scrollSnapType:'x mandatory', WebkitOverflowScrolling:'touch',
            margin:'0 -20px', padding:'2px 20px 8px', scrollPaddingLeft:20,
            cursor: showArrows ? 'grab' : 'default' }}>
          {presets.map(p => <PrtCard key={p.id} preset={p} onOpen={() => handleOpen(p.id)} isMobile={isMobile} />)}
        </div>
      </div>
      {!showArrows && (
        <div style={{ display:'flex', justifyContent:'center', marginTop:6 }}>{toggleBtn}</div>
      )}
    </>
  );
}

function PresetRoutinesSection() {
  const { navigate } = useRoute();
  const isMobile = useIsMobile();
  const presets = (typeof window !== 'undefined' && window.PRESET_ROUTINES) || [];
  const [openId, setOpenId] = React.useState(() => {
    try { const q = localStorage.getItem('atlas.preset.open'); if (q) { localStorage.removeItem('atlas.preset.open'); return q; } } catch {}
    return null;
  });
  const open = presets.find(p => p.id === openId) || null;
  // Lista ordenada por nivel: primero las de principiante.
  const sortedPresets = React.useMemo(
    () => presets.slice().sort((a, b) => (PR_LVL_ORDER[a.level] ?? 1) - (PR_LVL_ORDER[b.level] ?? 1)),
    [presets]
  );
  // El selector empieza en el nivel de la propia plantilla; si no lo define, en
  // el nivel del perfil. La selección solo afecta a la copia que se entrena.
  const [level, setLevel] = React.useState(prDefaultLevel);
  React.useEffect(() => { if (open) setLevel(open.level || prDefaultLevel()); }, [openId]); // eslint-disable-line react-hooks/exhaustive-deps

  // Both actions operate on ONE session (a day). Single-day routines pass dayIdx 0.
  function sessionOf(preset, dayIdx) {
    const days = prDays(preset);
    const day = days[Math.min(dayIdx || 0, days.length - 1)] || days[0];
    const multi = prIsMultiDay(preset);
    return {
      workout: prDayToWorkout(day, level),
      sessionName: multi ? `${preset.name} · ${day.name}` : (preset.subtitle || preset.name),
    };
  }
  function trainNow(preset, dayIdx) {
    const s = sessionOf(preset, dayIdx);
    try {
      localStorage.setItem('atlas.pendingWorkout', JSON.stringify(s.workout));
      localStorage.setItem('atlas.pendingWorkoutMeta', JSON.stringify({ mode: 'player', routineName: preset.name, sessionName: s.sessionName }));
    } catch (e) {}
    navigate('/player');
  }
  function loadBuilder(preset, dayIdx) {
    // Same shared format, no mode → Builder loads it into the editable canvas.
    const s = sessionOf(preset, dayIdx);
    try {
      localStorage.setItem('atlas.pendingWorkout', JSON.stringify(s.workout));
      localStorage.setItem('atlas.pendingWorkoutMeta', JSON.stringify({ routineName: preset.name, sessionName: s.sessionName }));
    } catch (e) {}
    navigate('/builder');
  }

  return (
    <div style={{ minHeight:'100vh', background:PRT.page, padding:'28px 20px 100px' }}>
      <div style={{ maxWidth:720, margin:'0 auto' }}>
        {!open ? (
          <>
            <div style={{ marginBottom:20 }}>
              <button onClick={() => navigate('/rutinas')} style={{ background:'none', border:`1px solid ${PRT.border}`, borderRadius:9, color:PRT.sub,
                padding:'6px 12px', cursor:'pointer', fontFamily:'Inter,system-ui', fontSize:12, fontWeight:700, marginBottom:14 }}>
                ← Mis rutinas
              </button>
              <div style={{ fontFamily:'Inter,system-ui', fontSize:24, fontWeight:800, color:PRT.text, letterSpacing:-0.6 }}>Rutinas predeterminadas</div>
              <div style={{ fontFamily:'Inter,system-ui', fontSize:13, color:PRT.sub, marginTop:4 }}>Plantillas basadas en ciencia — desliza para ver más.</div>
            </div>
            {/* Carousel with desktop arrows + "Ver todas" grid (mouse-reachable). */}
            <PrtCarousel presets={sortedPresets} onOpen={(id) => setOpenId(id)} isMobile={isMobile} />
          </>
        ) : (
          <PrtDetail preset={open} level={level} onLevel={setLevel} onBack={() => setOpenId(null)} onTrain={(d) => trainNow(open, d)} onLoadBuilder={(d) => loadBuilder(open, d)} />
        )}
      </div>
    </div>
  );
}

// Public template API — the single source of truth for turning a routine
// template into an executable session. Future Coach output can be handed to the
// Builder/Player in exactly this shape (this only prepares the format; it does
// not change the current Coach behaviour).
//   PresetTemplates.exercise(pe)            → normalized rich template exercise
//   PresetTemplates.resolve(id, level)      → level-appropriate variant id
//   PresetTemplates.toShared(pe, level)     → executable exercise (Builder/Player)
//   PresetTemplates.days(preset)            → [{ name, exercises, muscleMap? }]
//   PresetTemplates.sessionToWorkout(day, level) → executable session
window.PresetTemplates = {
  exercise: prTemplateExercise,
  resolve: prResolveLevel,
  toShared: prExToShared,
  days: prDays,
  sessionToWorkout: prDayToWorkout,
  isMultiDay: prIsMultiDay,
};

Object.assign(window, { PresetRoutinesSection, RtDetail });
