// ---- Post-meditation feeling check-in ----
// Looks for today's meditation calendar event and PUTs the feeling rating on it.
function MeditationCheckIn() {
  const [feeling, setFeeling] = React.useState(null);
  const [note, setNote] = React.useState('');
  const [saved, setSaved] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [eventId, setEventId] = React.useState(null);

  React.useEffect(() => {
    const today = new Date().toISOString().split('T')[0];
    api.get(`/api/calendar/month?start=${today}&end=${today}`)
      .then(data => {
        const events = data?.events || [];
        const todayMeditation = events.find(e => e.date === today && e.type === 'meditation');
        if (todayMeditation) setEventId(todayMeditation.id);
      })
      .catch(() => {});
  }, []);

  if (saved) return (
    <div style={{ marginTop: 12, fontSize: 13, color: 'var(--accent-green)' }}>
      ✓ Check-in saved{feeling ? ` — ${['😞','😕','😐','🙂','😄'][feeling-1]} ${feeling}/5` : ''}
    </div>
  );

  const save = async (skip) => {
    setSaving(true);
    try {
      if (!skip && feeling) {
        let id = eventId;
        if (!id) {
          // No planned session on the calendar — record the completed meditation anyway.
          const today = new Date().toISOString().split('T')[0];
          const created = await api.post('/api/calendar/event', {
            date: today, type: 'meditation', title: 'Meditation session',
          });
          id = created?.id;
        }
        if (id) {
          await api.put(`/api/calendar/event/${id}`, {
            completed: true, feeling, feeling_note: note.trim(),
          });
        }
      }
      setSaved(true);
    } catch (e) {
      console.error('Meditation check-in save error:', e);
      setSaved(true); // don't trap the user in the form
    } finally {
      setSaving(false);
    }
  };

  return (
    <div style={{ marginTop: 14, padding: '12px', borderRadius: 10, background: 'var(--bg-primary)', border: '1px solid var(--border)' }}>
      <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>How did your session feel?</div>
      <div style={{ display: 'flex', gap: 10, marginBottom: 10 }}>
        {['😞','😕','😐','🙂','😄'].map((emoji, idx) => (
          <button key={idx} onClick={() => setFeeling(idx + 1)} style={{
            fontSize: 22, background: 'none', border: feeling === idx + 1 ? '2px solid var(--accent-purple)' : '2px solid transparent',
            borderRadius: 8, cursor: 'pointer', padding: '2px 4px',
          }}>{emoji}</button>
        ))}
      </div>
      {feeling && (
        <input
          placeholder="Optional note (max 140 chars)"
          value={note}
          onChange={e => setNote(e.target.value.slice(0, 140))}
          style={{ width: '100%', boxSizing: 'border-box', fontSize: 12, padding: '6px 8px', background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text-primary)', marginBottom: 8 }}
        />
      )}
      <div style={{ display: 'flex', gap: 8 }}>
        <button className="btn btn-primary btn-sm" onClick={() => save(false)} disabled={saving || !feeling}>Save</button>
        <button className="btn btn-sm" onClick={() => save(true)} style={{ background: 'var(--surface-2)', color: 'var(--text-secondary)', border: '1px solid var(--border)' }}>Skip</button>
      </div>
    </div>
  );
}

// ---- Meditation Audio Player ----
function MeditationPlayer({ text, duration, lang }) {
  const totalSeconds = (duration || 20) * 60;
  const [status, setStatus] = React.useState('idle'); // idle | playing | paused | done
  const [elapsed, setElapsed] = React.useState(0);
  const [ambient, setAmbient] = React.useState('dreamscape');
  const [voiceVol, setVoiceVol] = React.useState(1.0);
  const [ambientVol, setAmbientVol] = React.useState(0.35);

  const VOICES = lang === 'es' ? [
    { id: 'es-MX-DaliaNeural', label: 'Dalia (MX)' },
    { id: 'es-MX-JorgeNeural', label: 'Jorge (MX)' },
    { id: 'es-ES-ElviraNeural', label: 'Elvira (ES)' },
    { id: 'es-ES-AlvaroNeural', label: 'Álvaro (ES)' },
  ] : [
    { id: 'en-GB-SoniaNeural', label: 'Sonia (UK)' },
    { id: 'en-US-AriaNeural', label: 'Aria (US)' },
    { id: 'en-US-MichelleNeural', label: 'Michelle (US)' },
    { id: 'en-US-GuyNeural', label: 'Guy (US)' },
    { id: 'en-US-ChristopherNeural', label: 'Christopher (US)' },
    { id: 'en-AU-NatashaNeural', label: 'Natasha (AU)' },
  ];
  const [voiceId, setVoiceId] = React.useState(() => {
    const saved = localStorage.getItem('meditation_voice');
    return VOICES.some(v => v.id === saved) ? saved : VOICES[0].id;
  });

  const audioCtxRef = React.useRef(null);
  const masterGainRef = React.useRef(null);
  const ambientNodesRef = React.useRef([]);
  const ambientAudioRef = React.useRef(null);
  const timerRef = React.useRef(null);
  const speechRef = React.useRef({ segments: [], index: 0, pauseTimeout: null, active: false, pauseSec: 8, volume: 1, cache: {}, currentAudio: null });

  // Split the meditation into short spoken segments — one or two sentences each —
  // so silence can be inserted between them. Headings are dropped (spoken
  // structure labels break the meditative flow). The first two sections
  // (purpose + setup) are flagged `intro`: they're read back-to-back so the
  // session starts immediately; long silences belong to the practice itself.
  const speechSegments = React.useMemo(() => {
    const cleanText = (s) => s
      .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
      .replace(/\*\*([^*]+)\*\*/g, '$1')
      .replace(/\*([^*]+)\*/g, '$1')
      // Metadata lines are visual aids, not narration (after bold stripping
      // so "**Duration:** 20 min" is caught too)
      .replace(/^\s*(duration|time|style|technique|focus|duración|tiempo|estilo|técnica|enfoque)\s*:.*$/gim, '')
      .replace(/^\s*[-—–|]+\s*$/gm, '')
      .replace(/^\s*\|.*\|\s*$/gm, '')
      .replace(/^\d+\.\s+/gm, '')
      .replace(/^[-*]\s+/gm, '')
      // Timing annotations: "(2 minutes)", "(~5 min)", "[30 sec]", "0:00–5:00", "2-3 min:"
      .replace(/[([][^)\]]*\b\d+\s*[-–—]?\s*\d*\s*(minutes?|mins?|seconds?|secs?|minutos?|segundos?)\b[^)\]]*[)\]]/gi, '')
      .replace(/\b\d{1,2}:\d{2}\s*[-–—]\s*\d{1,2}:\d{2}\b:?/g, '')
      .replace(/^~?\d+\s*[-–—]?\s*\d*\s*(minutes?|mins?|minutos?)\s*:\s*/gim, '')
      // Emojis and symbols aren't speech
      .replace(/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{FE0F}]/gu, '')
      .replace(/[`>_#~]/g, '')
      // Tidy gaps left by the removals so TTS doesn't stumble
      .replace(/[ \t]+([.,:;!?])/g, '$1')
      .replace(/[ \t]{2,}/g, ' ')
      .trim();

    const sections = text.split(/^#{1,6}\s+.*$/m);
    const segments = [];
    sections.forEach((sec, si) => {
      const intro = si <= 2;
      sec.split(/\n\s*\n/).forEach(rawPara => {
        // Explicit practice durations like "(3–6 min)" or "(30 seconds)" mean
        // "do this exercise for that long" — captured here (from the raw text,
        // before cleaning strips them) and applied as the silent wait after
        // the instruction is read. Ranges use their midpoint.
        let waitSec = 0;
        const dm = rawPara.match(/[([]\s*(?:about\s+|~)?(\d+)(?:\s*[-–—]\s*(\d+))?\s*(min(?:ute)?s?|minutos?|sec(?:ond)?s?|segundos?)\s*[)\]]/i);
        if (dm) {
          const lo = parseInt(dm[1], 10);
          const hi = dm[2] ? parseInt(dm[2], 10) : lo;
          const mid = (lo + hi) / 2;
          waitSec = Math.min(600, /min/i.test(dm[3]) ? mid * 60 : mid);
        }

        const para = cleanText(rawPara);
        const sentences = para.replace(/\s+/g, ' ').trim().match(/[^.!?…]+[.!?…]+|[^.!?…]+$/g) || [];
        const paraSegs = [];
        let buf = '';
        sentences.forEach(s => {
          buf = buf ? buf + ' ' + s.trim() : s.trim();
          if (buf.length > 90) { paraSegs.push({ text: buf, intro }); buf = ''; }
        });
        if (buf) paraSegs.push({ text: buf, intro });
        // The wait belongs after the whole instruction, not mid-paragraph
        if (paraSegs.length && waitSec) paraSegs[paraSegs.length - 1].waitSec = waitSec;
        segments.push(...paraSegs);
      });
    });
    return segments.filter(s => s.text.length > 2);
  }, [text]);

  function pickVoice(voiceLang) {
    const voices = window.speechSynthesis.getVoices() || [];
    const langVoices = voices.filter(v => v.lang && v.lang.toLowerCase().startsWith(voiceLang));
    const pool = langVoices.length ? langVoices : voices;
    const preferred = ['samantha', 'karen', 'moira', 'tessa', 'serena', 'allison', 'ava', 'zoe',
      'paulina', 'monica', 'google us english', 'google uk english female', 'google español'];
    for (const name of preferred) {
      const v = pool.find(v => v.name.toLowerCase().includes(name));
      if (v) return v;
    }
    const enhanced = pool.find(v => /enhanced|premium|natural/i.test(v.name));
    return enhanced || pool[0] || null;
  }

  // Neural TTS from the server (Edge voices) — cached per segment as blob URLs,
  // prefetched one ahead so playback is gapless.
  function getSegmentAudio(i) {
    const sp = speechRef.current;
    if (i >= sp.segments.length) return Promise.resolve(null);
    if (!sp.cache[i]) {
      // Retry transient failures — a single failed request must not drop a
      // segment to the robotic browser voice mid-session.
      sp.cache[i] = (async () => {
        let lastErr;
        for (let attempt = 0; attempt < 3; attempt++) {
          try {
            const headers = await window.api._getHeaders();
            const r = await fetch('/api/tts', {
              method: 'POST',
              headers,
              body: JSON.stringify({ text: sp.segments[i].text, lang: lang === 'es' ? 'es' : 'en', voice: sp.voice }),
            });
            if (!r.ok) throw new Error('tts ' + r.status);
            const blob = await r.blob();
            if (blob.size === 0) throw new Error('tts empty');
            return URL.createObjectURL(blob);
          } catch (e) {
            lastErr = e;
            await new Promise(res => setTimeout(res, 1000 * (attempt + 1)));
          }
        }
        throw lastErr;
      })();
      sp.cache[i].catch(() => { delete sp.cache[i]; });
    }
    return sp.cache[i];
  }

  function getCtx() {
    if (!audioCtxRef.current || audioCtxRef.current.state === 'closed') {
      audioCtxRef.current = new (window.AudioContext || window.webkitAudioContext)();
    }
    if (audioCtxRef.current.state === 'suspended') audioCtxRef.current.resume();
    return audioCtxRef.current;
  }

  function stopAmbient() {
    ambientNodesRef.current.forEach(n => {
      try { n.stop && n.stop(); } catch(e) {}
      try { n.disconnect && n.disconnect(); } catch(e) {}
    });
    ambientNodesRef.current = [];
    if (ambientAudioRef.current) {
      ambientAudioRef.current.pause();
      ambientAudioRef.current.src = '';
      ambientAudioRef.current = null;
    }
  }

  function setAmbientGain(v) {
    if (masterGainRef.current) masterGainRef.current.gain.value = v;
    if (ambientAudioRef.current) ambientAudioRef.current.volume = Math.min(1, v);
  }

  function playEndBell(ctx) {
    [528, 660, 792].forEach((freq, i) => {
      const osc = ctx.createOscillator();
      const g = ctx.createGain();
      osc.frequency.value = freq;
      g.gain.setValueAtTime(0, ctx.currentTime + i * 1.2);
      g.gain.linearRampToValueAtTime(0.4, ctx.currentTime + i * 1.2 + 0.05);
      g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + i * 1.2 + 6);
      osc.connect(g); g.connect(ctx.destination);
      osc.start(ctx.currentTime + i * 1.2);
      osc.stop(ctx.currentTime + i * 1.2 + 6);
    });
  }

  // Real recorded tracks (CC0 / CC BY — see client/public/audio/CREDITS.md),
  // served from /audio/ and looped. Binaural stays synthesized: the 6 Hz beat
  // requires exact L/R frequencies, which generated oscillators guarantee.
  const AMBIENT_TRACKS = {
    bowls: '/audio/bowls.mp3',
    rain: '/audio/rain.mp3',
    dreamscape: '/audio/dreamscape.mp3',
    cosmic: '/audio/cosmic.mp3',
  };

  function startAmbient(type, vol) {
    stopAmbient();
    masterGainRef.current = null;
    if (type === 'none') return;

    if (AMBIENT_TRACKS[type]) {
      const el = new Audio(AMBIENT_TRACKS[type]);
      el.loop = true;
      el.volume = Math.min(1, vol);
      el.play().catch(() => {});
      ambientAudioRef.current = el;
      return;
    }

    if (type === 'binaural') {
      const ctx = getCtx();
      const master = ctx.createGain();
      master.gain.value = vol;
      master.connect(ctx.destination);
      masterGainRef.current = master;
      const nodes = ambientNodesRef.current;
      [[200, -1], [206, 1]].forEach(([freq, pan]) => {
        const osc = ctx.createOscillator();
        const panner = ctx.createStereoPanner();
        const g = ctx.createGain();
        osc.frequency.value = freq; panner.pan.value = pan; g.gain.value = 0.2;
        osc.connect(g); g.connect(panner); panner.connect(master); osc.start();
        nodes.push(osc, g, panner);
      });
    }
  }

  function startSpeech(vol) {
    const sp = speechRef.current;
    window.speechSynthesis?.cancel();
    sp.segments = speechSegments;
    sp.index = 0;
    sp.active = true;
    sp.volume = vol;
    sp.voice = voiceId;
    sp.cache = {};

    // Spread the practice segments across the session: estimate total speaking
    // time (~1.6 words/sec at the slowed rate), subtract the intro (read
    // back-to-back), distribute the remaining silence over the practice
    // segments. Clamped to 4–35s so guidance never feels rushed or abandoned.
    const wordsOf = s => s.text.split(/\s+/).length;
    const estSpeakSec = sp.segments.reduce((acc, s) => acc + wordsOf(s), 0) / 1.6;
    const introCount = sp.segments.filter(s => s.intro).length;
    // Segments with an explicit practice duration own their silence; the
    // distributed pauses only fill what's left between the others.
    const explicitWaitSec = sp.segments.reduce((acc, s) => acc + (s.waitSec || 0), 0);
    const mainCount = Math.max(1, sp.segments.filter(s => !s.intro && !s.waitSec).length);
    const silenceBudget = Math.max(0, totalSeconds - estSpeakSec - introCount * 1.5 - explicitWaitSec - 30);
    sp.pauseSec = Math.min(35, Math.max(4, silenceBudget / mainCount));

    speakNextSegment();
  }

  function advanceSegment(seg) {
    const sp = speechRef.current;
    if (!sp.active) return;
    sp.index += 1;
    let base;
    if (seg.waitSec) {
      // Explicit practice duration ("(3–6 min)") — stay silent while the
      // listener actually does the exercise.
      base = seg.waitSec;
    } else if (seg.intro) {
      base = 1.5;
    } else {
      const isBreathCue = /breath|inhale|exhale|inhala|exhala|respira/i.test(seg.text);
      base = sp.pauseSec + (isBreathCue ? 6 : 0);
    }
    sp.pauseTimeout = setTimeout(speakNextSegment, base * 1000);
  }

  async function speakNextSegment() {
    const sp = speechRef.current;
    if (!sp.active || sp.index >= sp.segments.length) return;
    const myIndex = sp.index;
    const seg = sp.segments[myIndex];
    // Prefetch two ahead — the long pauses give retries room to recover
    // before a segment is actually needed.
    getSegmentAudio(myIndex + 1).catch(() => {});
    getSegmentAudio(myIndex + 2).catch(() => {});

    try {
      const url = await getSegmentAudio(myIndex);
      if (!sp.active || sp.index !== myIndex) return;
      const audio = new Audio(url);
      audio.volume = Math.min(1, sp.volume);
      audio.onended = () => { sp.currentAudio = null; advanceSegment(seg); };
      sp.currentAudio = audio;
      await audio.play();
      return;
    } catch (e) {
      // Server TTS unavailable — fall back to the browser voice.
    }

    if (!sp.active || sp.index !== myIndex || !window.speechSynthesis) return;
    const utter = new SpeechSynthesisUtterance(seg.text);
    const voice = pickVoice(lang === 'es' ? 'es' : 'en');
    if (voice) utter.voice = voice;
    utter.rate = 0.72;
    utter.pitch = 0.9;
    utter.volume = sp.volume;
    utter.onend = () => advanceSegment(seg);
    window.speechSynthesis.speak(utter);
  }

  function stopSpeech() {
    const sp = speechRef.current;
    sp.active = false;
    clearTimeout(sp.pauseTimeout);
    if (sp.currentAudio) { sp.currentAudio.pause(); sp.currentAudio = null; }
    window.speechSynthesis?.cancel();
    Object.values(sp.cache).forEach(p => p.then(url => URL.revokeObjectURL(url)).catch(() => {}));
    sp.cache = {};
  }

  function startTimer() {
    clearInterval(timerRef.current);
    timerRef.current = setInterval(() => {
      setElapsed(e => {
        const next = e + 1;
        if (next >= totalSeconds) {
          clearInterval(timerRef.current);
          stopSpeech();
          stopAmbient();
          try { playEndBell(getCtx()); } catch(err) {}
          setStatus('done');
          return totalSeconds;
        }
        return next;
      });
    }, 1000);
  }

  function handlePlay() {
    setElapsed(0);
    startAmbient(ambient, ambientVol);
    startSpeech(voiceVol);
    startTimer();
    setStatus('playing');
  }

  function handlePause() {
    clearInterval(timerRef.current);
    const sp = speechRef.current;
    sp.active = false;
    clearTimeout(sp.pauseTimeout);
    if (sp.currentAudio) sp.currentAudio.pause();
    // Browser-voice fallback can't pause cleanly mid-utterance — cancel and
    // restart the segment on resume instead.
    window.speechSynthesis?.cancel();
    setAmbientGain(ambientVol * 0.2);
    setStatus('paused');
  }

  function handleResume() {
    const sp = speechRef.current;
    sp.active = true;
    if (sp.currentAudio) sp.currentAudio.play().catch(() => {});
    else speakNextSegment();
    setAmbientGain(ambientVol);
    startTimer();
    setStatus('playing');
  }

  function handleStop() {
    clearInterval(timerRef.current);
    stopSpeech();
    stopAmbient();
    setStatus('idle');
    setElapsed(0);
  }

  React.useEffect(() => () => {
    clearInterval(timerRef.current);
    stopSpeech();
    stopAmbient();
  }, []);

  // Voice lists load asynchronously on some browsers — warm them up so the
  // first Play already gets the preferred voice instead of the default.
  React.useEffect(() => {
    if (window.speechSynthesis) {
      window.speechSynthesis.getVoices();
      window.speechSynthesis.onvoiceschanged = () => window.speechSynthesis.getVoices();
    }
  }, []);

  // Warm the server-side TTS cache for the whole session + selected voice as
  // soon as the text is shown, so the first Play is instant and resilient.
  React.useEffect(() => {
    if (!speechSegments.length) return;
    let cancelled = false;
    (async () => {
      try {
        const headers = await window.api._getHeaders();
        if (cancelled) return;
        await fetch('/api/tts/pregenerate', {
          method: 'POST',
          headers,
          body: JSON.stringify({
            segments: speechSegments.map(s => s.text),
            lang: lang === 'es' ? 'es' : 'en',
            voice: voiceId,
          }),
        });
      } catch (e) { /* best-effort warm-up */ }
    })();
    return () => { cancelled = true; };
  }, [speechSegments, voiceId, lang]);

  const pct = Math.min(100, (elapsed / totalSeconds) * 100);
  const fmt = s => `${Math.floor(s/60).toString().padStart(2,'0')}:${(s%60).toString().padStart(2,'0')}`;

  const ambientOpts = [
    { id: 'none', label: 'None', icon: '🔇' },
    { id: 'bowls', label: 'Singing Bowls', icon: '🔔' },
    { id: 'rain', label: 'Rain', icon: '🌧️' },
    { id: 'dreamscape', label: 'Dreamscape', icon: '🎵' },
    { id: 'cosmic', label: 'Cosmic', icon: '🌌' },
    { id: 'binaural', label: 'Binaural', icon: '🎧' },
  ];

  return (
    <div style={{ borderTop: '1px solid var(--border)', marginTop: 16, paddingTop: 16 }}>
      <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Voice</div>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 14 }}>
        {VOICES.map(v => (
          <button key={v.id} onClick={() => {
            setVoiceId(v.id);
            localStorage.setItem('meditation_voice', v.id);
            const sp = speechRef.current;
            sp.voice = v.id;
            // Drop prefetched audio so upcoming segments use the new voice.
            Object.values(sp.cache).forEach(p => p.then(url => URL.revokeObjectURL(url)).catch(() => {}));
            sp.cache = {};
          }} style={{
            padding: '3px 10px', borderRadius: 14, fontSize: 12, cursor: 'pointer',
            background: voiceId === v.id ? 'var(--accent-purple-dim)' : 'var(--surface-2)',
            color: voiceId === v.id ? 'var(--accent-purple)' : 'var(--text-secondary)',
            border: voiceId === v.id ? '1px solid var(--accent-purple)' : '1px solid var(--border)',
          }}>🎙️ {v.label}</button>
        ))}
      </div>

      <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Background sound</div>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 14 }}>
        {ambientOpts.map(o => (
          <button key={o.id} onClick={() => {
            setAmbient(o.id);
            if (status === 'playing') { stopAmbient(); if (o.id !== 'none') startAmbient(o.id, ambientVol); }
          }} style={{
            padding: '3px 10px', borderRadius: 14, fontSize: 12, cursor: 'pointer',
            background: ambient === o.id ? 'var(--accent-purple-dim)' : 'var(--surface-2)',
            color: ambient === o.id ? 'var(--accent-purple)' : 'var(--text-secondary)',
            border: ambient === o.id ? '1px solid var(--accent-purple)' : '1px solid var(--border)',
          }}>{o.icon} {o.label}</button>
        ))}
      </div>

      {status !== 'idle' && (
        <div style={{ display: 'flex', gap: 20, marginBottom: 12 }}>
          {[['🎙️ Voice', voiceVol, setVoiceVol, false],
            ['🎵 Ambient', ambientVol, setAmbientVol, true]].map(([label, val, setter, isAmbient]) => (
            <label key={label} style={{ fontSize: 11, color: 'var(--text-muted)', display: 'flex', alignItems: 'center', gap: 6 }}>
              {label}
              <input type="range" min="0" max="1" step="0.05" value={val} style={{ width: 80 }}
                onChange={e => {
                  const v = parseFloat(e.target.value); setter(v);
                  if (isAmbient) setAmbientGain(v);
                  else {
                    speechRef.current.volume = v;
                    if (speechRef.current.currentAudio) speechRef.current.currentAudio.volume = Math.min(1, v);
                  }
                }} />
            </label>
          ))}
        </div>
      )}

      <div style={{ marginBottom: 12 }}>
        <div style={{ background: 'var(--surface-2)', borderRadius: 4, height: 5, overflow: 'hidden' }}>
          <div style={{ width: `${pct}%`, height: '100%', background: 'var(--accent-purple)', transition: 'width 1s linear' }} />
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--text-muted)', marginTop: 3 }}>
          <span>{fmt(elapsed)}</span>
          <span>{fmt(totalSeconds)}</span>
        </div>
      </div>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
        {(status === 'idle' || status === 'done') && (
          <button className="btn btn-primary btn-sm" onClick={handlePlay}>
            ▶ {status === 'done' ? 'Play Again' : 'Play Guided Session'}
          </button>
        )}
        {status === 'playing' && <>
          <button className="btn btn-sm" onClick={handlePause}>⏸ Pause</button>
          <button className="btn btn-sm" onClick={handleStop} style={{ color: 'var(--text-muted)' }}>■ Stop</button>
        </>}
        {status === 'paused' && <>
          <button className="btn btn-primary btn-sm" onClick={handleResume}>▶ Resume</button>
          <button className="btn btn-sm" onClick={handleStop} style={{ color: 'var(--text-muted)' }}>■ Stop</button>
        </>}
        {status === 'done' && <span style={{ fontSize: 12, color: 'var(--accent-green)' }}>✓ Session complete</span>}
        {status !== 'idle' && ambient === 'binaural' && (
          <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>🎧 Use headphones for binaural effect</span>
        )}
      </div>
      {/* Post-meditation feeling check-in */}
      {status === 'done' && <MeditationCheckIn />}
    </div>
  );
}

// ---- Wellness Page ----
const SYMPTOM_LIST = [
  { id: 'cramps',      icon: '😣', label: 'Cramps' },
  { id: 'headache',    icon: '🤕', label: 'Headache' },
  { id: 'bloating',    icon: '😮‍💨', label: 'Bloating' },
  { id: 'low_energy',  icon: '🔋', label: 'Low energy' },
  { id: 'high_energy', icon: '⚡', label: 'High energy' },
  { id: 'mood_low',    icon: '😔', label: 'Low mood' },
  { id: 'mood_high',   icon: '😊', label: 'Great mood' },
  { id: 'sore_breasts',icon: '💜', label: 'Sore breasts' },
  { id: 'insomnia',    icon: '🌙', label: 'Insomnia' },
  { id: 'cravings',    icon: '🍫', label: 'Cravings' },
];

window.WellnessPage = function WellnessPage({ config, onConfigChange, t }) {
  const [meditationContent, setMeditationContent] = React.useState(null);
  const [yogaContent, setYogaContent] = React.useState(null);
  const [loadingMeditation, setLoadingMeditation] = React.useState(false);
  const [loadingYoga, setLoadingYoga] = React.useState(false);
  const [lastPeriodDate, setLastPeriodDate] = React.useState(null);
  const [periodDateInput, setPeriodDateInput] = React.useState(
    new Date().toISOString().split('T')[0]
  );
  const [savingPeriod, setSavingPeriod] = React.useState(false);

  // Symptom log state
  const [selectedSymptoms, setSelectedSymptoms] = React.useState([]);
  const [selectedEnergy, setSelectedEnergy] = React.useState(null);
  const [savingSymptoms, setSavingSymptoms] = React.useState(false);
  const [symptomsSaved, setSymptomsSaved] = React.useState(false);

  // Load today's meditation session
  React.useEffect(() => {
    if (!config?.meditation_enabled) return;
    setLoadingMeditation(true);
    api.get('/api/meditation/today')
      .then(result => {
        if (result.enabled && result.content) {
          setMeditationContent(result.content);
        }
      })
      .catch(() => {})
      .finally(() => setLoadingMeditation(false));
  }, [config?.meditation_enabled]);

  // Load today's yoga session
  React.useEffect(() => {
    if (!config?.yoga_enabled) return;
    setLoadingYoga(true);
    api.get('/api/yoga/today')
      .then(result => {
        if (result.enabled && result.content) {
          setYogaContent(result.content);
        }
      })
      .catch(() => {})
      .finally(() => setLoadingYoga(false));
  }, [config?.yoga_enabled]);

  // Load latest cycle log
  React.useEffect(() => {
    if (!config?.cycle_tracking_enabled) return;
    api.get('/api/cycle-logs/latest')
      .then(result => {
        if (result && result.date) {
          setLastPeriodDate(result.date);
        }
      })
      .catch(() => {});
  }, [config?.cycle_tracking_enabled]);

  // Load today's symptom log
  React.useEffect(() => {
    api.get('/api/symptoms?days=1')
      .then(logs => {
        const today = new Date().toISOString().split('T')[0];
        const todayLog = Array.isArray(logs) ? logs.find(l => l.date === today) : null;
        if (todayLog) {
          setSelectedSymptoms(todayLog.symptoms || []);
          setSelectedEnergy(todayLog.energy || null);
          setSymptomsSaved(true);
        }
      })
      .catch(() => {});
  }, []);

  const generateMeditation = async () => {
    setLoadingMeditation(true);
    try {
      const result = await api.get('/api/meditation/today');
      if (result.content) setMeditationContent(result.content);
    } catch (err) {
      console.error('Failed to generate meditation session:', err);
    } finally {
      setLoadingMeditation(false);
    }
  };

  const generateYoga = async () => {
    setLoadingYoga(true);
    try {
      const result = await api.get('/api/yoga/today');
      if (result.content) setYogaContent(result.content);
    } catch (err) {
      console.error('Failed to generate yoga session:', err);
    } finally {
      setLoadingYoga(false);
    }
  };

  const toggleSymptom = (id) => {
    setSymptomsSaved(false);
    setSelectedSymptoms(prev =>
      prev.includes(id) ? prev.filter(s => s !== id) : [...prev, id]
    );
  };

  const saveSymptoms = async () => {
    setSavingSymptoms(true);
    try {
      await api.post('/api/symptoms', {
        date: new Date().toISOString().split('T')[0],
        symptoms: selectedSymptoms,
        energy: selectedEnergy,
      });
      setSymptomsSaved(true);
    } catch (err) {
      console.error('Failed to save symptoms:', err);
    } finally {
      setSavingSymptoms(false);
    }
  };

  const logPeriod = async () => {
    if (!periodDateInput) return;
    setSavingPeriod(true);
    try {
      const result = await api.post('/api/cycle-logs', { date: periodDateInput });
      if (result && result.date) setLastPeriodDate(result.date);
    } catch (err) {
      console.error('Failed to log period:', err);
    } finally {
      setSavingPeriod(false);
    }
  };

  const toggleModule = async (key, checked) => {
    try {
      const updated = await api.put('/api/config', { [key]: checked });
      onConfigChange(updated);
    } catch (err) {
      console.error('Failed to update config:', err);
    }
  };

  const anyEnabled = config?.meditation_enabled || config?.yoga_enabled || config?.cycle_tracking_enabled;

  // Cycle phase computation
  const getCyclePhase = (lastDate) => {
    if (!lastDate) return null;
    const daysSince = Math.floor((new Date() - new Date(lastDate)) / 86400000);
    if (daysSince <= 5) return 'menstrual';
    if (daysSince <= 13) return 'follicular';
    if (daysSince <= 16) return 'ovulation';
    if (daysSince <= 24) return 'early_mid_luteal';
    return 'late_luteal';
  };

  return (
    <div>
      <div className="page-header">
        <h2>{t('wellnessPage')}</h2>
        <p>{t('todayWellness')}: {new Date().toISOString().split('T')[0]}</p>
      </div>

      {/* Section 0: Daily symptom quick-log */}
      <div className="card" style={{ marginBottom: 16, borderColor: 'rgba(139,92,246,0.15)' }}>
        <div className="card-header">
          <div className="card-title">🌸 How are you feeling today?</div>
          {symptomsSaved && (
            <span className="card-badge" style={{ background: 'rgba(34,197,94,0.15)', color: '#4ade80' }}>Saved ✓</span>
          )}
        </div>
        <div style={{ padding: '4px 0 8px' }}>
          <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Symptoms</div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 12 }}>
            {SYMPTOM_LIST.map(s => (
              <button key={s.id} onClick={() => toggleSymptom(s.id)} style={{
                padding: '4px 10px', borderRadius: 14, fontSize: 12, cursor: 'pointer',
                background: selectedSymptoms.includes(s.id) ? 'var(--accent-purple-dim)' : 'var(--surface-2)',
                color: selectedSymptoms.includes(s.id) ? 'var(--accent-purple)' : 'var(--text-secondary)',
                border: selectedSymptoms.includes(s.id) ? '1px solid var(--accent-purple)' : '1px solid var(--border)',
              }}>{s.icon} {s.label}</button>
            ))}
          </div>
          <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Energy level</div>
          <div style={{ display: 'flex', gap: 8, marginBottom: 14 }}>
            {[1,2,3,4,5].map(n => (
              <button key={n} onClick={() => { setSelectedEnergy(n); setSymptomsSaved(false); }} style={{
                width: 36, height: 36, borderRadius: '50%', fontSize: 14, cursor: 'pointer',
                background: selectedEnergy === n ? 'var(--accent-purple)' : 'var(--surface-2)',
                color: selectedEnergy === n ? '#fff' : 'var(--text-secondary)',
                border: selectedEnergy === n ? '2px solid var(--accent-purple)' : '1px solid var(--border)',
                fontFamily: 'JetBrains Mono',
              }}>{n}</button>
            ))}
          </div>
          <button className="btn btn-primary btn-sm" onClick={saveSymptoms} disabled={savingSymptoms}>
            {savingSymptoms ? 'Saving…' : 'Save check-in'}
          </button>
        </div>
      </div>

      {/* Section 1: No modules guard */}
      {!anyEnabled && (
        <div className="card" style={{ marginBottom: 16 }}>
          <div className="card-header">
            <div className="card-title">{t('noWellnessModules')}</div>
          </div>
          <p style={{ color: 'var(--text-muted)', fontSize: 13, padding: '8px 0' }}>
            {t('enableWellnessModules')}
          </p>
        </div>
      )}

      {/* Section 2: Today's Meditation */}
      {config?.meditation_enabled === true && (
        <div className="card" style={{ marginBottom: 16, borderColor: 'rgba(139,92,246,0.3)' }}>
          <div className="card-header">
            <div className="card-title" style={{ color: 'var(--accent-purple)' }}>
              {'🧘'} {t('meditationTitle')}
            </div>
          </div>
          {loadingMeditation ? (
            <div style={{ display: 'flex', justifyContent: 'center', padding: 20 }}>
              <div className="spinner" style={{ width: 18, height: 18 }} />
            </div>
          ) : meditationContent ? (
            <div className="analysis-content">
              <Markdown text={meditationContent} />
              <MeditationPlayer text={meditationContent} duration={config?.meditation_duration || 20} lang={config?.language || 'en'} />
            </div>
          ) : (
            <div style={{ padding: '12px 0' }}>
              <p style={{ color: 'var(--text-muted)', fontSize: 13, marginBottom: 12 }}>
                {t('sessionNotYetGenerated')}
              </p>
              <button
                className="btn btn-primary btn-sm"
                onClick={generateMeditation}
                disabled={loadingMeditation}
              >
                {loadingMeditation
                  ? <><div className="spinner" style={{ width: 14, height: 14 }} /> {t('generatingSession')}</>
                  : t('generateSession')}
              </button>
            </div>
          )}
        </div>
      )}

      {/* Section 3: Today's Yoga */}
      {config?.yoga_enabled === true && (
        <div className="card" style={{ marginBottom: 16, borderColor: 'rgba(16,185,129,0.3)' }}>
          <div className="card-header">
            <div className="card-title" style={{ color: 'var(--accent-green)' }}>
              {'🌿'} {t('yogaTitle')}
            </div>
            {(config.yoga_styles || []).length > 0 && (
              <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                {(config.yoga_styles || []).map(s => (
                  <span key={s} className="card-badge" style={{ fontSize: 10 }}>{s}</span>
                ))}
              </div>
            )}
          </div>
          {loadingYoga ? (
            <div style={{ display: 'flex', justifyContent: 'center', padding: 20 }}>
              <div className="spinner" style={{ width: 18, height: 18 }} />
            </div>
          ) : yogaContent ? (
            <div className="analysis-content">
              <Markdown text={yogaContent} />
            </div>
          ) : (
            <div style={{ padding: '12px 0' }}>
              <p style={{ color: 'var(--text-muted)', fontSize: 13, marginBottom: 12 }}>
                {t('sessionNotYetGenerated')}
              </p>
              <button
                className="btn btn-primary btn-sm"
                onClick={generateYoga}
                disabled={loadingYoga}
              >
                {loadingYoga
                  ? <><div className="spinner" style={{ width: 14, height: 14 }} /> {t('generatingSession')}</>
                  : t('generateSession')}
              </button>
            </div>
          )}
        </div>
      )}

      {/* Section 4: Cycle Log */}
      {config?.cycle_tracking_enabled === true && (
        <div className="card" style={{ marginBottom: 16, borderColor: 'rgba(139,92,246,0.2)' }}>
          <div className="card-header">
            <div className="card-title">{t('cycleTrackingTitle')}</div>
            {lastPeriodDate && (() => {
              const phaseKey = getCyclePhase(lastPeriodDate);
              return phaseKey ? (
                <span className="card-badge" style={{
                  background: 'var(--accent-purple-dim)',
                  color: 'var(--accent-purple)',
                }}>
                  {t('cyclePhase_' + phaseKey)}
                </span>
              ) : null;
            })()}
          </div>
          <div style={{ padding: '8px 0' }}>
            <div style={{ marginBottom: 12 }}>
              <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('lastPeriodDate')}: </span>
              <span style={{ fontSize: 13, fontFamily: 'JetBrains Mono' }}>
                {lastPeriodDate || '--'}
              </span>
            </div>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
              <input
                type="date"
                className="input"
                style={{ flex: '0 0 auto', fontSize: 13 }}
                value={periodDateInput}
                onChange={e => setPeriodDateInput(e.target.value)}
              />
              <button
                className="btn btn-primary btn-sm"
                onClick={logPeriod}
                disabled={savingPeriod || !periodDateInput}
              >
                {savingPeriod ? t('savingEllipsis') : t('logPeriodStart')}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Section 5: Module Toggles (always shown) */}
      <div className="card" style={{ marginBottom: 16 }}>
        <div className="card-header">
          <div className="card-title">{t('wellnessModules')}</div>
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12, padding: '8px 0' }}>
          <label style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}>
            <input
              type="checkbox"
              checked={!!config?.meditation_enabled}
              onChange={e => toggleModule('meditation_enabled', e.target.checked)}
            />
            <span style={{ fontSize: 14 }}>{'🧘'} {t('meditationLabel')}</span>
          </label>
          <label style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}>
            <input
              type="checkbox"
              checked={!!config?.yoga_enabled}
              onChange={e => toggleModule('yoga_enabled', e.target.checked)}
            />
            <span style={{ fontSize: 14 }}>{'🌿'} {t('yogaLabel')}</span>
          </label>
          <label style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}>
            <input
              type="checkbox"
              checked={!!config?.cycle_tracking_enabled}
              onChange={e => toggleModule('cycle_tracking_enabled', e.target.checked)}
            />
            <span style={{ fontSize: 14 }}>&#128100; {t('cycleTrackingLabel')}</span>
          </label>
        </div>
      </div>
    </div>
  );
};
