// session.jsx — active sitting: scan sweep, breathing, tap-to-note, timer, audio.

function fmt(s) {
  const m = Math.floor(s / 60), ss = Math.floor(s % 60);
  return `${m}:${String(ss).padStart(2, '0')}`;
}

// ── practice phase plans (fractions of total duration) ──────────
const MT_PLANS = {
  anapana:                  [['anapana', 0, 1]],
  anapana_vipassana:        [['anapana', 0, 0.25], ['vipassana', 0.25, 1]],
  anapana_vipassana_metta:  [['anapana', 0, 0.2], ['vipassana', 0.2, 0.86], ['metta', 0.86, 1]],
  vipassana:                [['vipassana', 0, 1]],
  open:                     [['open', 0, 1]],
  walking:                  [['walking', 0, 1]],
  bhakti:                   [['bhakti', 0, 1]],
  hybrid:                   [['hybrid', 0, 1]],
};
const MT_PHASE = {
  anapana:   { hue: '#56B4FF', label: 'Ānāpāna',   short: 'breath' },
  vipassana: { hue: '#36E8D2', label: 'Vipassanā', short: 'body sweep' },
  metta:     { hue: '#FF9EC4', label: 'Mettā',     short: 'lovingkindness' },
  open:      { hue: '#B49CFF', label: 'Open awareness', short: 'noting' },
  walking:   { hue: '#8FD0A8', label: 'Walking', short: 'the steps' },
  bhakti:    { hue: '#FFB24D', label: 'Naam', short: 'the Name' },
  hybrid:    { hue: '#C9A0FF', label: 'Naam-scan', short: 'the Name' },
};
const MT_PHASE_CUES = {
  anapana: [
    'Rest attention at the rim of the nostrils.',
    'Feel the breath enter — cool. Leave — warm.',
    'Just the breath. Nothing to add, nothing to fix.',
    'When the mind wanders, gently return to the breath.',
    'Sharpen the attention on this small triangle of skin.',
  ],
  vipassana: [
    'Move attention slowly through the body, part by part.',
    'Whatever arises — pleasant or unpleasant — simply observe.',
    'Anicca. Every sensation is changing, passing.',
    'No reaction. No craving, no aversion. Only observe.',
    'Stay equanimous, knowing it will change.',
  ],
  metta: [
    'Let the body soften, the heart grow warm.',
    'May I be happy. May I be peaceful. May I be free.',
    'May all beings be happy. May all beings be free.',
    'Radiate goodwill outward, in every direction.',
    'Rest in the warmth, open and unguarded.',
  ],
  open: [
    'Sit. Know the body as it is.',
    'When a thought arises, note it softly — and let it pass.',
    'Whatever you are doing, be aware of it.',
    'Rest in open awareness. Nothing to chase.',
  ],
  walking: [
    'Feel each foot as it lifts, moves, and places.',
    'Walk a little slower than feels natural.',
    'When the mind wanders, return to the soles of the feet.',
    'Lifting. Moving. Placing.',
  ],
  bhakti: [
    'Repeat the Name, gently, with each breath.',
    'Let the Name fill the mind — leave no room for anything else.',
    'When a sensation or feeling arises, offer it to the Lord and return to the Name.',
    'No need to observe. Only to fill, and to surrender.',
    'The ego dissolves where the Name lives.',
  ],
  hybrid: [
    'Place the Name on this part of the body.',
    'Fill each cell with the Name in place of craving.',
    'Move slowly — let the Name saturate the whole body.',
    'Where sensation was, now let the Name be.',
    'Body and Name become one.',
  ],
};
const MT_PHASE_ENTER = {
  anapana: 'Begin with the breath. Ānāpāna.',
  vipassana: 'Now move to the body. Begin the sweep.',
  metta: 'Now, lovingkindness. Let the heart open.',
  open: 'Rest in open awareness.',
  walking: 'Begin walking. Attention in the feet.',
};

// deeper, "why-it-matters" reminders — appended after the base cues so
// the later spaced reminders progress into understanding, not repetition
const MT_DEEP = {
  anapana: [
    'Why such a small area? A narrow focus sharpens the mind, like a lens gathering light.',
    'Do not force the breath — only watch it. Long or short, simply know it as it is.',
    'Each time you notice the mind has wandered and return, the attention grows stronger.',
  ],
  vipassana: [
    'Why observe without reacting? Each craving or aversion plants a habit; calm observation dissolves one.',
    'Move at an even pace, part by part — the faint sensations matter as much as the strong.',
  ],
};

// region groups for the picker (top → bottom)
const MT_REGION_GROUPS = [
  ['Head & neck', ['head', 'throat']],
  ['Torso', ['chest', 'abdomen', 'pelvis']],
  ['Arms', ['armL', 'forearmL', 'handL', 'armR', 'forearmR', 'handR']],
  ['Legs', ['thighL', 'calfL', 'footL', 'thighR', 'calfR', 'footR']],
];

// Bottom sheet to note a sensation at a tapped region
function NoteSheet({ region, onClose, onSave, onRegionChange, startPick }) {
  const C = MT_COLORS;
  const [reg, setReg] = React.useState(region);
  const [sens, setSens] = React.useState(null);
  const [inten, setInten] = React.useState(1);
  const [emo, setEmo] = React.useState(null);
  const [shown, setShown] = React.useState(false);
  const [pickArea, setPickArea] = React.useState(!!startPick);
  React.useEffect(() => { const t = setTimeout(() => setShown(true), 24); return () => clearTimeout(t); }, []);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') { e.stopPropagation(); onClose(); } };
    window.addEventListener('keydown', onKey, true);
    return () => window.removeEventListener('keydown', onKey, true);
    // eslint-disable-next-line
  }, []);
  // follow taps on the body while the sheet is open
  React.useEffect(() => { setReg(region); }, [region]);
  const choose = (id) => { setReg(id); onRegionChange && onRegionChange(id); setPickArea(false); };
  const label = MT_REGION_LABEL[reg] || reg;
  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 40, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(2,6,11,0.55)', backdropFilter: 'blur(3px)', opacity: shown ? 1 : 0, transition: 'opacity .3s ease' }} />
      <div style={{
        position: 'relative', background: 'linear-gradient(180deg, rgba(13,32,40,0.96), rgba(7,18,26,0.98))',
        borderTop: `1px solid ${C.tealLine}`, borderRadius: '28px 28px 0 0',
        padding: '14px 20px 26px', boxShadow: '0 -20px 50px rgba(0,0,0,0.5)',
        transform: shown ? 'translateY(0)' : 'translateY(100%)',
        transition: 'transform .34s cubic-bezier(.2,.85,.25,1)',
        maxHeight: '86%', overflowY: 'auto',
      }}>
        <div style={{ width: 40, height: 4, borderRadius: 4, background: C.inkGhost, margin: '0 auto 16px' }} />
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
          <button onClick={() => setPickArea(p => !p)} style={{ appearance: 'none', background: 'none', border: 'none', padding: 0, cursor: 'pointer', textAlign: 'left' }}>
            <MTEyebrow>Noting at · tap to change</MTEyebrow>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
              <span style={{ width: 9, height: 9, borderRadius: 9, background: C.teal, boxShadow: `0 0 10px ${C.teal}` }} />
              <span style={{ fontSize: 22, fontWeight: 600, color: C.ink, letterSpacing: 0.2 }}>{label}</span>
              <MTIcon name={pickArea ? 'chevL' : 'chevR'} size={18} color={C.inkFaint} />
            </div>
          </button>
          <button onClick={onClose} style={{ appearance: 'none', background: 'rgba(255,255,255,0.05)', border: 'none', borderRadius: 999, width: 36, height: 36, display: 'grid', placeItems: 'center', cursor: 'pointer', color: C.inkDim }}>
            <MTIcon name="close" size={18} />
          </button>
        </div>

        {/* region picker — choose which area */}
        {pickArea ? (
          <div style={{ marginBottom: 20 }}>
            <div style={{ fontSize: 13, color: C.inkFaint, marginBottom: 12 }}>Where in the body? <span style={{ opacity: 0.6 }}>— or tap the figure</span></div>
            {MT_REGION_GROUPS.map(([title, ids]) => (
              <div key={title} style={{ marginBottom: 14 }}>
                <div style={{ fontFamily: 'var(--mono)', fontSize: 10.5, letterSpacing: 1.5, textTransform: 'uppercase', color: C.inkFaint, marginBottom: 8 }}>{title}</div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7 }}>
                  {ids.map(id => (
                    <MTChip key={id} label={MT_REGION_LABEL[id] || id} active={reg === id} onClick={() => choose(id)} />
                  ))}
                </div>
              </div>
            ))}
          </div>
        ) : (
        <React.Fragment>
        <div style={{ fontSize: 13, color: C.inkFaint, marginBottom: 9 }}>What is the sensation?</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 20 }}>
          {MT_SENSATIONS.map(s => (
            <MTChip key={s.id} label={s.label} color={s.hue} active={sens === s.id} onClick={() => setSens(s.id)} />
          ))}
        </div>

        <div style={{ fontSize: 13, color: C.inkFaint, marginBottom: 9 }}>How strong?</div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 20 }}>
          {MT_INTENSITY.map((lab, i) => (
            <button key={lab} onClick={() => setInten(i)} style={{
              flex: 1, appearance: 'none', cursor: 'pointer', font: 'inherit',
              padding: '12px 0', borderRadius: 14, textTransform: 'capitalize',
              border: `1px solid ${inten === i ? C.teal : C.inkGhost}`,
              background: inten === i ? 'rgba(54,232,210,0.12)' : 'rgba(255,255,255,0.02)',
              color: inten === i ? C.ink : C.inkDim, fontSize: 14, fontWeight: 600,
              display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6,
            }}>
              <span style={{ display: 'flex', gap: 3 }}>
                {[0,1,2].map(d => <span key={d} style={{ width: 6, height: 6 + d * 4, borderRadius: 2, background: d <= i ? C.teal : C.inkGhost }} />)}
              </span>
              {lab}
            </button>
          ))}
        </div>

        <div style={{ fontSize: 13, color: C.inkFaint, marginBottom: 9 }}>Any feeling-tone? <span style={{ opacity: 0.6 }}>(optional)</span></div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7, marginBottom: 22 }}>
          {MT_EMOTIONS.map(e => (
            <MTChip key={e} label={e} sub active={emo === e} onClick={() => setEmo(emo === e ? null : e)} />
          ))}
        </div>

        <MTButton full size="lg" onClick={() => sens && onSave({ region: reg, sensationId: sens, intensity: inten, emotion: emo })}
          style={{ opacity: sens ? 1 : 0.4, pointerEvents: sens ? 'auto' : 'none' }}>
          <MTIcon name="check" size={20} color="#022019" /> Mark sensation
        </MTButton>
        </React.Fragment>
        )}
      </div>
    </div>
  );
}

function SessionScreen({ config, audio, onEnd, vp }) {
  const C = MT_COLORS;
  const [extraMin, setExtraMin] = React.useState(0);   // "+5" extensions during the sit
  const [cueEvery, setCueEvery] = React.useState(config.cueEvery != null ? config.cueEvery : 5); // minutes between repeated cues after the instructions; 0 = off
  const total = (config.duration + extraMin) * 60;
  // the RAF loop runs with [] deps — these refs keep it seeing live values
  const totalRef = React.useRef(total); totalRef.current = total;
  const finishRef = React.useRef(null);
  const [elapsed, setElapsed] = React.useState(0);
  const [paused, setPaused] = React.useState(true);          // held until the countdown ends
  const [countdown, setCountdown] = React.useState(5);       // 5-4-3-2-1, then the sit begins
  const [showKeys, setShowKeys] = React.useState(false);     // "?" shortcut overlay
  const [dimmed, setDimmed] = React.useState(false);         // chrome fades when idle
  React.useEffect(() => {
    let t;
    const poke = () => { setDimmed(false); clearTimeout(t); t = setTimeout(() => setDimmed(true), 10000); };
    poke();
    const evs = ['pointermove', 'pointerdown', 'keydown', 'touchstart'];
    evs.forEach(ev => window.addEventListener(ev, poke));
    return () => { clearTimeout(t); evs.forEach(ev => window.removeEventListener(ev, poke)); };
  }, []);
  const firstPhaseRef = React.useRef(null);
  React.useEffect(() => {
    if (countdown === null) return;
    if (countdown <= 0) { setCountdown(null); setPaused(false); return; }
    const t = setTimeout(() => setCountdown(c => c - 1), 1000);
    return () => clearTimeout(t);
  }, [countdown]);
  // spacebar toggles pause (once the countdown is over)
  React.useEffect(() => {
    const onKey = (e) => {
      const tag = e.target && e.target.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
      if (e.code === 'Space') { e.preventDefault(); setPaused(p => (countdown === null ? !p : p)); }
      else if (e.key === 'm' || e.key === 'M') setMuted(m => !m);
      else if (e.key === 'v' || e.key === 'V') setVoiceRemind(on => { if (on) voice.cancel(); return !on; });
      else if (e.key === '?') setShowKeys(k => !k);
      else if (e.key === 'Escape') setShowKeys(false);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [countdown]);
  const [scanPos, setScanPos] = React.useState(40);
  const [randRegion, setRandRegion] = React.useState(null);
  const randRef = React.useRef({ region: null, t: -99 });
  const [markers, setMarkers] = React.useState([]);
  const [sheet, setSheet] = React.useState(null);
  const [sheetPick, setSheetPick] = React.useState(false);
  const [thought, setThought] = React.useState(false);
  const [parked, setParked] = React.useState(() => config.parked || []);
  const parkedRef = React.useRef(config.parked || []);
  const [muted, setMuted] = React.useState(config.ambient === false);
  const intervalSec = (config.intervalBell || 0) * 60;
  const bellRef = React.useRef(0);
  const voice = useVoice();
  const voiceOn = config.voice !== false && voice.supported;
  const ambLvl = ((config.ambientVol != null ? config.ambientVol : 55) / 100) * 0.4;   // 55 -> the old 0.22
  const voiceVol = (config.voiceVol != null ? config.voiceVol : 100) / 100;
  useWakeLock(true); // screen stays on for the whole sitting, pause included
  // two independent reminder sounds: gong + spoken cue. select gong frequency live.
  const GONGS = [['Deep', 288], ['Temple', 396], ['Bowl', 432], ['Bright', 528], ['Crystal', 639]];
  const [gongFreq, setGongFreq] = React.useState(config.gongFreq || 432);
  const [gongOn, setGongOn] = React.useState(config.intervalBell > 0 ? (config.reminderType !== 'voice') : false);
  const [voiceRemind, setVoiceRemind] = React.useState(config.intervalBell > 0 ? (config.reminderType === 'voice' || config.reminderType === 'both') : false);
  const gongFreqRef = React.useRef(gongFreq); React.useEffect(() => { gongFreqRef.current = gongFreq; }, [gongFreq]);
  const gongOnRef = React.useRef(gongOn); React.useEffect(() => { gongOnRef.current = gongOn; }, [gongOn]);
  const voiceRemindRef = React.useRef(voiceRemind); React.useEffect(() => { voiceRemindRef.current = voiceRemind; }, [voiceRemind]);
  const [cueIdx, setCueIdx] = React.useState(0);
  const elapsedRef = React.useRef(0);
  const lastRef = React.useRef(performance.now());
  const pausedRef = React.useRef(true);
  const persistRef = React.useRef(0);
  const doneRef = React.useRef(false);
  React.useEffect(() => { pausedRef.current = paused; }, [paused]);

  const pose = config.pose || 'standing';
  const P = mtPose(pose);
  const direction = config.direction || 'down';
  const SWEEP = config.speed || 95; // seconds per full sweep
  const axis = (direction === 'left' || direction === 'right') ? 'x' : 'y';
  const [a0, a1] = axis === 'x' ? P.scanX : P.scanY;

  // practice phase plan
  const practice = config.practice || (config.mode === 'silent' ? 'open' : 'anapana_vipassana');
  const PLAN = MT_PLANS[practice] || MT_PLANS.anapana_vipassana;
  const phaseKindAt = (frac) => { const p = PLAN.find(x => frac >= x[1] && frac < x[2]); return (p || PLAN[PLAN.length - 1])[0]; };
  const [manualPhase, setManualPhase] = React.useState(null); // user-chosen override
  const manualPhaseRef = React.useRef(null);
  React.useEffect(() => { manualPhaseRef.current = manualPhase; }, [manualPhase]);
  const autoPhase = phaseKindAt(total > 0 ? Math.min(0.999, elapsed / total) : 0);
  const phaseKind = manualPhase || autoPhase;
  const phaseInfo = MT_PHASE[phaseKind] || MT_PHASE.vipassana;
  const tintHue = phaseInfo.hue;
  const isBhakti = phaseKind === 'bhakti';
  const isHybrid = phaseKind === 'hybrid';
  const deity = MT_DEITIES[config.deity] || MT_DEITIES.vitthala;
  const sweepActive = phaseKind === 'vipassana' || isHybrid;
  const guided = sweepActive; // sweep + region detection during Vipassanā or Naam-scan
  const phaseRef = React.useRef(null);
  const [naamRegions, setNaamRegions] = React.useState([]);
  const naamRef = React.useRef({ last: null });
  const headPt = ((P.byId[P.headId] || {}).point) || [P.aura.cx, 60];
  const anchor = phaseKind === 'anapana' ? { x: headPt[0], y: headPt[1] + 16 } : null;

  // active region under the sweep (or the randomly chosen one)
  const scanRegion = React.useMemo(() => {
    if (!sweepActive) return null;
    if (direction === 'random') return randRegion;
    let best = null, bd = 1e9;
    for (const r of P.regions) {
      const c = axis === 'x' ? r.point[0] : r.point[1];
      const dd = Math.abs(c - scanPos);
      if (dd < bd) { bd = dd; best = r; }
    }
    return bd < (axis === 'x' ? 38 : 42) ? best.id : null;
  }, [scanPos, randRegion, sweepActive, direction, axis, pose]);

  // main RAF loop (subscribes once; reads paused via ref)
  React.useEffect(() => {
    let raf;
    lastRef.current = performance.now();
    const tick = (now) => {
      const dt = (now - lastRef.current) / 1000;
      lastRef.current = now;
      if (!pausedRef.current && !doneRef.current) {
        const e = elapsedRef.current + dt;
        elapsedRef.current = e;
        setElapsed(e);
        // heartbeat so an interrupted sit can be recovered on next open
        if (e - (persistRef.current || 0) >= 5) {
          persistRef.current = e;
          mtSaveActiveSit({ at: Date.now(), elapsedSec: Math.round(e), duration: config.duration });
        }
        const frac = totalRef.current > 0 ? Math.min(0.999, e / totalRef.current) : 0;
        const fk = manualPhaseRef.current || phaseKindAt(frac);
        // phase transition → soft bell + fresh cue
        if (fk !== phaseRef.current) {
          if (phaseRef.current !== null && e > 0.5) {
            audio.bell(fk === 'metta' ? 528 : fk === 'vipassana' ? 432 : 480);
            setCueIdx(i => i + 1);
          }
          phaseRef.current = fk;
        }
        if (fk === 'vipassana' || fk === 'hybrid') {
          if (direction === 'random') {
            const stepEvery = Math.max(1.6, SWEEP / 12);
            if (e - randRef.current.t > stepEvery) {
              randRef.current.t = e;
              const regs = P.regions;
              let pick;
              do { pick = regs[Math.floor(Math.random() * regs.length)].id; }
              while (pick === randRef.current.region && regs.length > 1);
              randRef.current.region = pick;
              setRandRegion(pick);
            }
          } else {
            const phase = (e % SWEEP) / SWEEP;
            let pos;
            if (direction === 'up' || direction === 'left') pos = a1 - phase * (a1 - a0);
            else if (direction === 'mix') pos = phase < 0.5 ? a0 + (phase / 0.5) * (a1 - a0) : a1 - ((phase - 0.5) / 0.5) * (a1 - a0);
            else pos = a0 + phase * (a1 - a0); // down / right
            setScanPos(pos);
          }
        }
        if (e >= totalRef.current) { finishRef.current(e); return; }
        // periodic reminder — gong and/or gentle voice (independent)
        if (intervalSec > 0) {
          const k = Math.floor(e / intervalSec);
          if (k > bellRef.current && e < totalRef.current - 1) {
            bellRef.current = k;
            if (gongOnRef.current) audio.bell(gongFreqRef.current);
            if (voiceRemindRef.current && voiceOn) { const sh = (MT_PHASE[fk] || {}).short; if (sh) voice.speak('Gently return your attention to ' + sh + '.', { volume: voiceVol }); }
          }
        }
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
    // eslint-disable-next-line
  }, []);

  // start audio + bell
  React.useEffect(() => {
    audio.bell(528);
    audio.fade(muted ? 0 : ambLvl, 3);
    return () => audio.fade(0, 1.2);
    // eslint-disable-next-line
  }, []);
  React.useEffect(() => { audio.fade(muted || paused ? 0 : ambLvl, 1.2); }, [muted, paused, ambLvl]); // eslint-disable-line

  // phase-aware rotating guidance cues
  const cues = React.useMemo(() => {
    const pool = MT_PHASE_CUES[phaseKind] || MT_PHASE_CUES.vipassana;
    if (isHybrid && scanRegion) {
      return [`Place “${deity.chant}” on the ${(MT_REGION_LABEL[scanRegion] || '').toLowerCase()}.`, ...pool];
    }
    if (phaseKind === 'vipassana' && scanRegion) {
      return [`Rest attention on the ${(MT_REGION_LABEL[scanRegion] || '').toLowerCase()}.`, ...pool];
    }
    return pool;
  }, [phaseKind, scanRegion, isHybrid]);
  // Hybrid: accumulate the Name on each region the sweep passes (filling the body with naama)
  React.useEffect(() => {
    if (!isHybrid) { if (naamRegions.length) setNaamRegions([]); return; }
    if (!scanRegion || scanRegion === naamRef.current.last) return;
    naamRef.current.last = scanRegion;
    setNaamRegions(prev => {
      const kept = prev.filter(r => r.id !== scanRegion).map(r => ({ id: r.id, fresh: false }));
      const next = [...kept, { id: scanRegion, fresh: true }];
      return next.slice(-6); // keep the last several visible at once
    });
  }, [scanRegion, isHybrid]);
  React.useEffect(() => {
    const id = setInterval(() => setCueIdx(i => i + 1), 7000);
    return () => clearInterval(id);
  }, []);
  const cue = cues[cueIdx % cues.length];

  // Voice arc per phase:
  //  OPENING — a quick, complete instruction in the first ~30s (settle +
  //  the practice's first cues), spoken once and never repeated.
  //  REMINDERS — afterwards, the remaining cues plus deeper "why" lines,
  //  one DIFFERENT line every `cueEvery` minutes (off = opening only).
  // At the session start the opening is the full settle+instruction; a
  // later phase change gets just its brief enter line, then reminders.
  const planRef = React.useRef({ phase: null });
  const firstPhaseDoneRef = React.useRef(false);
  React.useEffect(() => { if (!paused) { const p = planRef.current; if (p.remAt) p.remAt = Date.now() - (p._held || 0); } }, [paused]);
  React.useEffect(() => {
    if (!voiceOn || paused || countdown !== null) return;
    const now = Date.now();
    let vp = planRef.current;
    if (vp.phase !== phaseKind) {
      const isFirst = !firstPhaseDoneRef.current; firstPhaseDoneRef.current = true;
      const cues = MT_PHASE_CUES[phaseKind] || [];
      const enter = MT_PHASE_ENTER[phaseKind] || cues[0];
      const deep = MT_DEEP[phaseKind] || [];
      const opening = isFirst
        ? ['Let the body settle. Let the eyes close.', cues[0], cues[1]].filter(Boolean)
        : [enter].filter(Boolean);
      const reminders = (isFirst ? cues.slice(2) : cues.slice(0)).concat(deep);
      vp = planRef.current = { phase: phaseKind, openAt: now, openIdx: 1, remIdx: 0, remAt: now, opening, reminders };
      if (opening.length) voice.speak(opening[0], { volume: voiceVol });   // first line right away
      return;
    }
    const since = now - vp.openAt;
    // opening lines, ~11s apart, all inside the first ~30s
    if (vp.openIdx < vp.opening.length) {
      if (since >= vp.openIdx * 11000 + 1000) {
        voice.speak(vp.opening[vp.openIdx], { volume: voiceVol });
        vp.openIdx++; vp.remAt = now;
      }
      return;
    }
    // progressive reminders — different each time, governed by cueEvery
    if (!cueEvery || !vp.reminders.length) return;
    if (since < 30000) return;                       // let the opening breathe ~30s
    if (now - vp.remAt < cueEvery * 60 * 1000) return;
    vp.remAt = now;
    voice.speak(vp.reminders[vp.remIdx % vp.reminders.length], { volume: voiceVol });
    vp.remIdx++;
    // eslint-disable-next-line
  }, [cueIdx, phaseKind, voiceOn, paused, countdown, cueEvery]);
  // a soft pulse when the practice phase changes (eyes-closed signal)
  React.useEffect(() => {
    if (firstPhaseRef.current === null) { firstPhaseRef.current = phaseKind; return; }
    if (firstPhaseRef.current === phaseKind) return;
    firstPhaseRef.current = phaseKind;
    try { if (navigator.vibrate) navigator.vibrate(60); } catch (e) {}
    // eslint-disable-next-line
  }, [phaseKind]);
  // stop speaking on pause; resume happens naturally on next cue
  React.useEffect(() => { if (paused) voice.cancel(); }, [paused]); // eslint-disable-line
  React.useEffect(() => () => voice.cancel(), []); // cleanup on unmount

  function finish(e) {
    if (doneRef.current) return;
    doneRef.current = true;
    mtClearActiveSit();
    voice.cancel();
    audio.bell(396);
    onEnd({ duration: config.duration, elapsedSec: Math.round(e || elapsed), markers, parked: parkedRef.current });
  }
  function addThought(text) {
    const t = (text || '').trim(); if (!t) return;
    const next = [...parkedRef.current, { id: 'p' + Date.now() + Math.random().toString(36).slice(2, 6), text: t }];
    parkedRef.current = next; setParked(next); mtSaveParked(next);
    audio.pluck(520, 0.06);
    setThought(false);
  }
  function saveMark(m) {
    audio.bell(660);
    setMarkers(prev => {
      const i = prev.filter(p => p.region === m.region).length;
      return [...prev, { ...m, i }];
    });
    setSheet(null);
  }

  finishRef.current = finish;
  const remaining = Math.max(0, total - elapsed);
  const prog = Math.min(1, elapsed / total);

  return (
    <div style={{ position: 'fixed', inset: 0, background: `radial-gradient(120% 80% at 50% 12%, #0a1c2a 0%, ${C.bg} 62%)`, color: C.ink, overflow: 'hidden' }}>
      <ParticleField count={50} />

      {/* "?" keyboard shortcuts */}
      {showKeys && (
        <div onClick={() => setShowKeys(false)} style={{ position: 'absolute', inset: 0, zIndex: 45, display: 'grid', placeItems: 'center', background: 'rgba(4,9,15,0.6)', backdropFilter: 'blur(3px)', cursor: 'pointer' }}>
          <div style={{ background: 'rgba(10,22,30,0.95)', border: `1px solid ${C.inkGhost}`, borderRadius: 16, padding: '18px 26px', fontFamily: 'var(--mono)', fontSize: 14, color: C.inkDim, lineHeight: 2.1 }}>
            <div><span style={{ color: C.ink }}>space</span> &nbsp;pause / resume</div>
            <div><span style={{ color: C.ink }}>m</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mute ambient</div>
            <div><span style={{ color: C.ink }}>v</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;voice reminders</div>
            <div><span style={{ color: C.ink }}>?</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this panel</div>
          </div>
        </div>
      )}

      {/* settle-in countdown before anything begins */}
      {countdown !== null && (
        <div style={{ position: 'absolute', inset: 0, zIndex: 40, display: 'grid', placeItems: 'center',
          background: 'rgba(4,9,15,0.55)', backdropFilter: 'blur(3px)' }}>
          <div style={{ textAlign: 'center' }}>
            <div key={countdown} style={{ fontFamily: 'var(--mono)', fontSize: 96, lineHeight: 1, color: tintHue,
              textShadow: `0 0 40px ${hexA(tintHue, 0.45)}`, animation: 'mtNaamPop 0.9s ease both' }}>{countdown}</div>
            <div style={{ marginTop: 18, fontSize: 14, color: C.inkDim, fontFamily: 'var(--sans)', letterSpacing: 1.5, textTransform: 'uppercase' }}>Settle in</div>
          </div>
        </div>
      )}

      {/* top bar: timer + phase + exit */}
      <div style={{ position: 'absolute', top: 'calc(env(safe-area-inset-top, 0px) + 30px)', left: 0, right: 0, maxWidth: 640, margin: '0 auto', padding: '0 20px', boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'space-between', zIndex: 10, opacity: dimmed ? 0.07 : 1, transition: 'opacity 1.8s ease' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <button onClick={() => finish(elapsedRef.current)} style={{ appearance: 'none', background: 'rgba(255,255,255,0.05)', border: `1px solid ${C.inkGhost}`, borderRadius: 999, padding: '8px 14px', color: C.inkDim, cursor: 'pointer', fontSize: 13, fontFamily: 'var(--mono)' }}>End</button>
          <div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <div style={{ fontFamily: 'var(--mono)', fontSize: 26, letterSpacing: 1, color: C.ink, lineHeight: 1 }}>{fmt(remaining)}</div>
              <button onClick={() => setExtraMin(m => m + 5)} title="Sit five minutes longer"
                style={{ appearance: 'none', cursor: 'pointer', background: 'rgba(255,255,255,0.05)', border: `1px solid ${C.inkGhost}`,
                borderRadius: 999, padding: '3px 9px', color: C.inkDim, fontSize: 12, fontFamily: 'var(--mono)' }}>+5</button>
            </div>
            {/* practice / phase switcher */}
            <div style={{ position: 'relative', display: 'flex', marginTop: 3 }}>
              <select value={phaseKind} onChange={(e) => { const v = e.target.value; setManualPhase(v); audio.bell((MT_PHASE[v] || {}).hue ? 432 : 432); }} title="Switch practice"
                style={{ appearance: 'none', WebkitAppearance: 'none', background: 'transparent', border: 'none', borderRadius: 6, padding: '0 16px 0 0', color: tintHue, cursor: 'pointer', fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: 2, textTransform: 'uppercase', outline: 'none' }}>
                {Object.keys(MT_PHASE).map(k => <option key={k} value={k} style={{ color: '#000', textTransform: 'none', letterSpacing: 0 }}>{MT_PHASE[k].label}{sweepActive && k === phaseKind && scanRegion ? '' : ''}</option>)}
              </select>
              <span style={{ position: 'absolute', right: 2, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none', color: tintHue, fontSize: 8 }}>▾</span>
            </div>
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          {/* gong on/off */}
          <button onClick={() => { const on = !gongOn; setGongOn(on); if (on) audio.bell(gongFreq); }} title="Gong reminder"
            style={{ appearance: 'none', background: gongOn ? hexA(tintHue, 0.14) : 'rgba(255,255,255,0.05)', border: `1px solid ${gongOn ? hexA(tintHue, 0.5) : C.inkGhost}`, borderRadius: 999, width: 38, height: 38, display: 'grid', placeItems: 'center', color: gongOn ? tintHue : C.inkFaint, cursor: 'pointer' }}>
            <MTIcon name="timer" size={17} />
          </button>
          {/* gong tone dropdown */}
          <div style={{ position: 'relative', display: 'flex' }}>
            <select value={gongFreq} onChange={(e) => { const f = +e.target.value; setGongFreq(f); setGongOn(true); audio.bell(f); }} title="Gong tone"
              style={{ appearance: 'none', WebkitAppearance: 'none', background: 'rgba(255,255,255,0.05)', border: `1px solid ${C.inkGhost}`, borderRadius: 999, padding: '0 22px 0 11px', height: 38, color: C.inkDim, cursor: 'pointer', fontSize: 12, fontFamily: 'var(--mono)', outline: 'none' }}>
              {GONGS.map(([lab, f]) => <option key={f} value={f} style={{ color: '#000' }}>{lab}</option>)}
            </select>
            <span style={{ position: 'absolute', right: 8, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none', color: C.inkFaint, fontSize: 9 }}>▾</span>
          </div>
          {/* spoken-cue repeat interval — instructions always play first */}
          <button onClick={() => setCueEvery(v => ({ 3: 5, 5: 10, 10: 0, 0: 3 }[v]))}
            title="How often the voice repeats a cue after the opening instructions"
            style={{ appearance: 'none', background: cueEvery ? hexA(tintHue, 0.1) : 'rgba(255,255,255,0.05)', border: `1px solid ${cueEvery ? hexA(tintHue, 0.4) : C.inkGhost}`,
              borderRadius: 999, height: 38, padding: '0 11px', color: cueEvery ? tintHue : C.inkFaint, cursor: 'pointer', fontSize: 12, fontFamily: 'var(--mono)' }}>
            {cueEvery ? `cue ${cueEvery}m` : 'cue off'}
          </button>
          {/* voice guidance on/off */}
          {voice.supported && (
            <button onClick={() => { const on = !voiceRemind; setVoiceRemind(on); if (!on) voice.cancel(); }} title="Voice guidance"
              style={{ appearance: 'none', background: voiceRemind ? hexA(tintHue, 0.14) : 'rgba(255,255,255,0.05)', border: `1px solid ${voiceRemind ? hexA(tintHue, 0.5) : C.inkGhost}`, borderRadius: 999, width: 38, height: 38, display: 'grid', placeItems: 'center', color: voiceRemind ? tintHue : C.inkFaint, cursor: 'pointer' }}>
              <MTIcon name="quotes" size={17} />
            </button>
          )}
          {/* ambient mute */}
          <button onClick={() => setMuted(m => !m)} title="Ambient drone" style={{ appearance: 'none', background: 'rgba(255,255,255,0.05)', border: `1px solid ${C.inkGhost}`, borderRadius: 999, width: 38, height: 38, display: 'grid', placeItems: 'center', color: muted ? C.inkFaint : tintHue, cursor: 'pointer' }}>
            <MTIcon name={muted ? 'mute' : 'sound'} size={18} />
          </button>
        </div>
      </div>

      {/* progress hairline */}
      <div style={{ position: 'absolute', top: 'calc(env(safe-area-inset-top, 0px) + 82px)', left: '50%', transform: 'translateX(-50%)', width: 'min(100% - 40px, 600px)', height: 2, background: C.inkGhost, borderRadius: 2, zIndex: 10 }}>
        <div style={{ width: `${prog * 100}%`, height: '100%', background: `linear-gradient(90deg, ${tintHue}, ${hexA(tintHue, 0.5)})`, borderRadius: 2, boxShadow: `0 0 8px ${tintHue}`, transition: 'background .6s ease' }} />
      </div>

      {/* body stage */}
      <div style={{ position: 'absolute', top: 'calc(env(safe-area-inset-top, 0px) + 104px)', bottom: 132, left: 0, right: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        {/* breathing ring */}
        <div style={{ position: 'absolute', width: 'min(46vh, 280px)', height: 'min(46vh, 280px)', borderRadius: '50%', border: `1px solid ${hexA(tintHue, 0.3)}`,
          animation: paused ? 'none' : 'mtRing 9s ease-in-out infinite', filter: 'blur(0.3px)' }} />
        <div style={{ height: '100%', maxHeight: 'min(620px, 72vh)', aspectRatio: `${P.vb[2]}/${P.vb[3]}` }}>
          <BodyFigure
            pose={pose}
            scanRegion={scanRegion}
            selected={sheet}
            markers={markers}
            onRegionTap={(id) => { setSheet(id); setSheetPick(false); }}
            breathing={!paused}
            scanAxis={axis}
            scanPos={scanPos}
            showScanLine={sweepActive && !paused && direction !== 'random'}
            tint={tintHue}
            glow={phaseKind === 'metta' || isBhakti}
            anchor={anchor}
            naam={isHybrid ? { regions: naamRegions, word: deity.chant, hue: tintHue } : null}
          />
        </div>
        {/* chant Naam overlay — Bhakti shows the big centered Name; Hybrid places it on the body parts */}
        {isBhakti && (
          <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>
            <div key={Math.floor(elapsed / 4)} style={{ fontFamily: 'var(--serif)', fontSize: isBhakti ? 46 : 30, fontWeight: 300, color: tintHue,
              textShadow: `0 0 30px ${hexA(tintHue, 0.6)}`, animation: paused ? 'none' : 'mtChant 4s ease-in-out infinite', opacity: 0.92 }}>
              {deity.chant}
            </div>
          </div>
        )}
      </div>

      {/* guidance cue */}
      <div style={{ position: 'absolute', bottom: 118, left: '50%', transform: 'translateX(-50%)', width: 'min(100% - 48px, 600px)', textAlign: 'center', zIndex: 10, minHeight: 52 }}>
        <div key={cueIdx} style={{ fontFamily: 'var(--serif)', fontStyle: 'italic', fontSize: 19, lineHeight: 1.45, color: C.inkDim,
          animation: 'mtCue 7s ease-in-out', textWrap: 'pretty' }}>{cue}</div>
      </div>

      {/* controls */}
      <div style={{ position: 'absolute', bottom: 36, left: 0, right: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 18, zIndex: 10, opacity: dimmed ? 0.07 : 1, transition: 'opacity 1.8s ease' }}>
        <button onClick={() => setPaused(p => !p)} style={ctrlBtn(C, 56)}>
          <MTIcon name={paused ? 'play' : 'pause'} size={22} color={C.ink} />
        </button>
        <button onClick={() => setThought(true)} style={{ ...ctrlBtn(C, 56), border: `1px solid ${hexA('#FFC78A', 0.4)}`, background: 'rgba(255,199,138,0.08)' }} title="Park a thought">
          <MTIcon name="tray" size={21} color="#FFC78A" />
        </button>
        <button onClick={() => { setSheet(scanRegion || 'chest'); setSheetPick(true); }} style={{ ...ctrlBtn(C, 56), border: `1px solid ${hexA(C.teal, 0.45)}`, background: hexA(C.teal, 0.1) }} title="Note a sensation">
          <MTIcon name="plus" size={22} color={C.teal} />
        </button>
        <button onClick={() => finish()} style={ctrlBtn(C, 56)} title="End the sitting">
          <MTIcon name="check" size={22} color={C.ink} />
        </button>
      </div>
      <div style={{ position: 'absolute', bottom: 14, left: 0, right: 0, display: 'flex', justifyContent: 'center', gap: 18, fontSize: 10.5, color: C.inkFaint, fontFamily: 'var(--mono)', letterSpacing: 0.8, zIndex: 10 }}>
        <span style={{ display: 'flex', alignItems: 'center', gap: 5 }}><MTIcon name="tray" size={12} color={hexA('#FFC78A', 0.7)} /> PARK A THOUGHT</span>
        <span style={{ display: 'flex', alignItems: 'center', gap: 5 }}><MTIcon name="check" size={12} color={hexA(C.teal, 0.7)} /> {(isBhakti || isHybrid) ? 'TAP A SENSATION · OFFER IT' : 'END · MAP THE BODY AFTER'}</span>
      </div>

      {sheet && <NoteSheet region={sheet} startPick={sheetPick} onClose={() => { setSheet(null); setSheetPick(false); }} onSave={saveMark} onRegionChange={(id) => setSheet(id)} />}
      {thought && <ThoughtSheet count={parked.length} onClose={() => setThought(false)} onSave={addThought} />}
    </div>
  );
}

function ctrlBtn(C, sz) {
  return {
    appearance: 'none', cursor: 'pointer', width: sz, height: sz, borderRadius: 999,
    background: 'rgba(255,255,255,0.06)', border: `1px solid ${C.inkGhost}`,
    display: 'grid', placeItems: 'center', backdropFilter: 'blur(8px)',
  };
}

// quick mid-sit thought capture — set it down, return to the breath
function ThoughtSheet({ onClose, onSave, count }) {
  const C = MT_COLORS;
  const [val, setVal] = React.useState('');
  const [shown, setShown] = React.useState(false);
  const inputRef = React.useRef(null);
  React.useEffect(() => {
    const t = setTimeout(() => { setShown(true); inputRef.current && inputRef.current.focus(); }, 24);
    return () => clearTimeout(t);
  }, []);
  const amber = '#FFC78A';
  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 40, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(2,6,11,0.6)', backdropFilter: 'blur(3px)', opacity: shown ? 1 : 0, transition: 'opacity .3s ease' }} />
      <div style={{
        position: 'relative', background: 'linear-gradient(180deg, rgba(34,26,16,0.97), rgba(9,15,22,0.98))',
        borderTop: `1px solid ${hexA(amber, 0.4)}`, borderRadius: '28px 28px 0 0', padding: '14px 20px 26px',
        boxShadow: '0 -20px 50px rgba(0,0,0,0.5)', transform: shown ? 'translateY(0)' : 'translateY(100%)',
        transition: 'transform .34s cubic-bezier(.2,.85,.25,1)',
      }}>
        <div style={{ width: 40, height: 4, borderRadius: 4, background: C.inkGhost, margin: '0 auto 16px' }} />
        <div style={{ display: 'flex', alignItems: 'center', gap: 11, marginBottom: 14 }}>
          <div style={{ width: 38, height: 38, borderRadius: 11, display: 'grid', placeItems: 'center', background: hexA(amber, 0.12), color: amber, flexShrink: 0 }}>
            <MTIcon name="tray" size={20} color={amber} />
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 19, fontWeight: 600, color: C.ink, letterSpacing: 0.2 }}>Set it down</div>
            <div style={{ fontSize: 12.5, color: C.inkFaint }}>Note the thought, then return to the breath. It's yours again after.</div>
          </div>
          <button onClick={onClose} style={{ appearance: 'none', background: 'rgba(255,255,255,0.05)', border: 'none', borderRadius: 999, width: 36, height: 36, display: 'grid', placeItems: 'center', cursor: 'pointer', color: C.inkDim }}>
            <MTIcon name="close" size={18} />
          </button>
        </div>
        <textarea ref={inputRef} value={val} onChange={e => setVal(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); onSave(val); } }}
          placeholder="The thought that just arose…"
          style={{ width: '100%', boxSizing: 'border-box', minHeight: 84, resize: 'none', background: 'rgba(255,255,255,0.04)',
            border: `1px solid ${C.inkGhost}`, borderRadius: 16, padding: 14, color: C.ink, fontSize: 16, lineHeight: 1.5,
            fontFamily: 'var(--sans)', outline: 'none', marginBottom: 16 }} />
        <button onClick={() => onSave(val)} disabled={!val.trim()} style={{
          appearance: 'none', font: 'inherit', cursor: val.trim() ? 'pointer' : 'default', width: '100%', padding: '16px 22px',
          borderRadius: 999, border: 'none', background: val.trim() ? `linear-gradient(180deg, ${amber}, #E0A45C)` : 'rgba(255,255,255,0.06)',
          color: val.trim() ? '#241608' : C.inkFaint, fontSize: 16, fontWeight: 650, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 9,
          boxShadow: val.trim() ? `0 0 24px ${hexA(amber, 0.35)}` : 'none', transition: 'all .15s' }}>
          <MTIcon name="tray" size={19} color={val.trim() ? '#241608' : C.inkFaint} /> Set it aside{count ? ` · ${count} held` : ''}
        </button>
      </div>
    </div>
  );
}

Object.assign(window, { SessionScreen, NoteSheet, ThoughtSheet, fmt });
