// app.jsx — responsive shell: onboarding, adaptive nav, session + witness flows, persistence.

const { useState, useEffect, useMemo, useRef } = React;

function computeStreak(sessions) {
  if (!sessions.length) return 0;
  const days = new Set(sessions.map(s => new Date(s.date).toDateString()));
  let streak = 0;
  const d = new Date();
  if (!days.has(d.toDateString())) d.setDate(d.getDate() - 1);
  while (days.has(d.toDateString())) { streak++; d.setDate(d.getDate() - 1); }
  return streak;
}

// ── Onboarding ────────────────────────────────────────────────────
function Onboarding({ vp, onDone }) {
  const C = MT_COLORS;
  const [i, setI] = useState(0);
  const land = vp.landscape;
  const steps = [
    { eye: 'Welcome', title: 'A tool for real practice', tint: '#36E8D2',
      body: 'Anicca is a companion for serious meditation — not relaxation, but the work of freeing the mind. It is free, private, and lives only on your device.',
      demo: { breathing: true } },
    { eye: 'The body keeps the score', title: 'Stress is stored in the body', tint: '#36E8D2',
      body: 'Tension, fear and grief live in the body as sensation. They release only when you observe them objectively — untying the knots held deep in the body and subconscious mind.',
      demo: { markers: [{ region: 'chest', sensationId: 'tension', intensity: 2, i: 0 }, { region: 'throat', sensationId: 'pressure', intensity: 1, i: 0 }, { region: 'abdomen', sensationId: 'heat', intensity: 1, i: 0 }] } },
    { eye: 'Two doors, one room', title: 'Insight, devotion, or both', tint: '#C9A0FF',
      body: 'Insight (Vipassanā) empties the mind by seeing through impurity. Bhakti fills it with the divine Name so impurity has no room. Choose either path — or a hybrid that places the Name on every part of the body.',
      demo: { tint: '#C9A0FF', naam: { regions: [{ id: 'chest', fresh: true }, { id: 'head', fresh: false }, { id: 'abdomen', fresh: false }], word: 'Om', hue: '#C9A0FF' } } },
    { eye: 'The body remembers', title: 'Watch the pattern shift', tint: '#36E8D2',
      body: 'After each sit you map where the body held tension or ease. Over days the Atlas reveals where you store stress — and shows it beginning to move and soften.',
      demo: { heat: { chest: { hue: '#FF5C7A', weight: 1 }, throat: { hue: '#A98CFF', weight: 0.6 }, abdomen: { hue: '#FFB24D', weight: 0.5 }, handL: { hue: '#3CE6FF', weight: 0.4 }, handR: { hue: '#3CE6FF', weight: 0.3 } } } },
    { eye: 'Samvega — a sense of urgency', title: 'The time is now', tint: '#FF9E6B',
      body: 'Life is short and the work is real. Anicca shows the meditation time you actually have left — not to alarm you, but to return you, gently, to this breath. Begin.',
      demo: { tint: '#FF9E6B', breathing: true } },
  ];
  const st = steps[i];
  const copy = (
    <div style={{ position: 'relative', maxWidth: 460, textAlign: land ? 'left' : 'left' }}>
      <MTEyebrow color={st.tint || C.teal}>{st.eye}</MTEyebrow>
      <h1 style={{ font: '300 30px/1.15 var(--serif)', margin: '10px 0 12px' }}>{st.title}</h1>
      <p style={{ fontSize: 16, lineHeight: 1.6, color: C.inkDim, margin: '0 0 24px', textWrap: 'pretty' }}>{st.body}</p>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <div style={{ display: 'flex', gap: 7, flex: 1, maxWidth: 220 }}>
          {steps.map((_, k) => <span key={k} style={{ height: 4, flex: k === i ? 2 : 1, borderRadius: 4, background: k === i ? (st.tint || C.teal) : C.inkGhost, transition: 'all .3s' }} />)}
        </div>
        <MTButton onClick={() => i < steps.length - 1 ? setI(i + 1) : onDone()}>
          {i < steps.length - 1 ? 'Next' : 'Begin'} <MTIcon name="chevR" size={18} color="#022019" />
        </MTButton>
      </div>
    </div>
  );
  return (
    <div style={{ position: 'fixed', inset: 0, background: `radial-gradient(120% 80% at 50% 6%, #0a1c2a 0%, ${C.bg} 60%)`, color: C.ink, overflow: 'hidden' }}>
      <ParticleField count={44} />
      <button onClick={onDone} style={{ position: 'absolute', top: 28, right: 24, zIndex: 10, appearance: 'none', background: 'none', border: 'none', color: C.inkFaint, cursor: 'pointer', fontSize: 13, fontFamily: 'var(--mono)' }}>SKIP</button>
      <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: land ? 'row' : 'column', alignItems: 'center', justifyContent: 'center', gap: land ? 56 : 0, padding: land ? '0 6vw' : '74px 28px 40px' }}>
        <div style={{ display: 'grid', placeItems: 'center', flex: land ? '0 0 auto' : '1 1 auto', minHeight: 0 }}>
          <div style={{ height: land ? 'min(76vh,560px)' : 'min(46vh,360px)', aspectRatio: '300/660' }}>
            <BodyFigure interactive={false} breathing {...st.demo} />
          </div>
        </div>
        {copy}
      </div>
    </div>
  );
}

// ── adaptive navigation ───────────────────────────────────────────
// ── shareable URLs ────────────────────────────────────────────────
// #/today #/learn #/atlas #/diary #/teachings #/witness — tabs/flows
// #/sit/<practice>/<minutes> — opens Setup prefilled, e.g. #/sit/metta/20
const MT_SIT_TOKENS = {
  anapana:   { path: 'insight', practice: 'anapana' },
  sweep:     { path: 'insight', practice: 'anapana_vipassana' },
  vipassana: { path: 'insight', practice: 'anapana_vipassana' },
  full:      { path: 'insight', practice: 'anapana_vipassana_metta' },
  metta:     { path: 'insight', practice: 'anapana_vipassana_metta' },
  open:      { path: 'insight', practice: 'open' },
  walking:   { path: 'insight', practice: 'walking' },
  naam:      { path: 'bhakti' },
  naamscan:  { path: 'hybrid' },
};
function parseAppHash() {
  const seg = (window.location.hash || '').replace(/^#\/?/, '').split('/').filter(Boolean);
  if (!seg.length) return null;
  if (seg[0] === 'sit') {
    const tok = MT_SIT_TOKENS[seg[1]] || {};
    const min = Math.max(1, Math.min(180, parseInt(seg[2], 10) || 20));
    return { kind: 'sit', preset: { ...tok, duration: min } };
  }
  if (seg[0] === 'witness') return { kind: 'witness' };
  const tabs = { today: 'today', learn: 'learn', atlas: 'atlas', diary: 'journal', journal: 'journal', teachings: 'teachings' };
  if (tabs[seg[0]]) return { kind: 'tab', tab: tabs[seg[0]] };
  return null;
}

// items flagged `action` launch a flow instead of switching the tab
const NAV = [
  ['today', 'Today', 'today'],
  ['learn', 'Learn', 'lotus'],
  ['atlas', 'Atlas', 'atlas'],
  ['journal', 'Diary', 'book'],
  ['teachings', 'Teachings', 'quotes'],
];
const WITNESS_HUE = '#B49CFF';

function BottomTabBar({ tab, setTab, onWitness }) {
  const C = MT_COLORS;
  return (
    <div style={{ flexShrink: 0, paddingBottom: 'calc(env(safe-area-inset-bottom, 0px) + 14px)', paddingTop: 10,
      display: 'flex', justifyContent: 'space-around',
      background: 'linear-gradient(180deg, rgba(4,9,15,0) 0%, rgba(4,9,15,0.9) 45%, rgba(4,9,15,0.98) 100%)', backdropFilter: 'blur(10px)' }}>
      {NAV.map(([id, label, icon, action]) => {
        const active = !action && tab === id;
        const color = action ? WITNESS_HUE : active ? C.teal : C.inkFaint;
        return (
          <button key={id} onClick={() => action ? onWitness() : setTab(id)} style={{ appearance: 'none', background: 'none', border: 'none', cursor: 'pointer',
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5, padding: '4px 6px', color }}>
            <MTIcon name={icon} size={22} sw={active ? 2 : 1.6} />
            <span style={{ fontSize: 10, fontWeight: active ? 600 : 500, letterSpacing: 0.2 }}>{label}</span>
          </button>
        );
      })}
    </div>
  );
}

function TopNav({ tab, setTab, onWitness, onBegin }) {
  const C = MT_COLORS;
  return (
    <div style={{ flexShrink: 0, borderBottom: `1px solid ${C.stroke}`, background: 'rgba(5,11,17,0.6)', backdropFilter: 'blur(12px)' }}>
      <div style={{ maxWidth: 1080, margin: '0 auto', width: '100%', padding: '16px 32px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', boxSizing: 'border-box' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
          <span style={{ width: 11, height: 11, borderRadius: 11, background: C.teal, boxShadow: `0 0 12px ${C.teal}` }} />
          <span style={{ fontFamily: 'var(--serif)', fontSize: 22, fontWeight: 500, letterSpacing: 0.5 }}>Anicca</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
          {NAV.map(([id, label, icon, action]) => {
            const active = !action && tab === id;
            return (
              <button key={id} onClick={() => action ? onWitness() : setTab(id)} style={{ appearance: 'none', cursor: 'pointer', background: 'none', border: 'none',
                position: 'relative', padding: '8px 14px', display: 'flex', alignItems: 'center', gap: 7, fontSize: 15, fontWeight: active ? 600 : 500,
                color: action ? WITNESS_HUE : active ? C.ink : C.inkDim, fontFamily: 'var(--sans)' }}>
                {action && <MTIcon name="eye" size={17} color={WITNESS_HUE} />}
                {label}
                {active && <span style={{ position: 'absolute', left: 14, right: 14, bottom: 0, height: 2, borderRadius: 2, background: C.teal }} />}
              </button>
            );
          })}
          <div style={{ marginLeft: 12 }}>
            <MTButton size="sm" onClick={onBegin}><MTIcon name="play" size={16} color="#022019" /> Begin</MTButton>
          </div>
        </div>
      </div>
    </div>
  );
}

// ── App ───────────────────────────────────────────────────────────
function App() {
  const C = MT_COLORS;
  const vp = useViewport();
  const audio = useAmbient();
  const [onboarded, setOnboarded] = useState(() => localStorage.getItem('mt_onboarded') === '1');
  const [sessions, setSessions] = useState(() => mtLoadSessions());
  const [life, setLife] = useState(() => mtLoadLife());
  const [pose, setPose] = useState(() => mtLoadPose());
  const initRoute = React.useMemo(parseAppHash, []);
  const [tab, setTab] = useState(initRoute && initRoute.kind === 'tab' ? initRoute.tab : 'today');
  const [flow, setFlow] = useState(initRoute && initRoute.kind === 'sit' ? 'setup' : initRoute && initRoute.kind === 'witness' ? 'witness' : null);   // null | 'setup' | 'session' | 'complete' | 'witness' | 'lifetime'
  const [sitPreset, setSitPreset] = useState(initRoute && initRoute.kind === 'sit' ? initRoute.preset : null);
  const [config, setConfig] = useState(null);
  const [result, setResult] = useState(null);

  useEffect(() => { mtSaveSessions(sessions); }, [sessions]);
  useEffect(() => { mtSaveLife(life); }, [life]);
  useEffect(() => { mtSavePose(pose); }, [pose]);
  const streak = useMemo(() => computeStreak(sessions), [sessions]);

  function finishOnboard() { localStorage.setItem('mt_onboarded', '1'); setOnboarded(true); }
  const flowRef = React.useRef(null);
  React.useEffect(() => { flowRef.current = flow; }, [flow]);
  // back/forward + pasted links → state
  React.useEffect(() => {
    const onHash = () => {
      if (flowRef.current === 'session') return;   // never yank a live sitting
      const r = parseAppHash();
      if (!r) return;
      if (r.kind === 'tab') { setFlow(null); setTab(r.tab); }
      else if (r.kind === 'witness') setFlow('witness');
      else if (r.kind === 'sit') { setSitPreset(r.preset); setFlow('setup'); }
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  // state → address bar (replaceState avoids hashchange feedback loops)
  React.useEffect(() => {
    const TAB_HASH = { today: '#/today', learn: '#/learn', atlas: '#/atlas', journal: '#/diary', teachings: '#/teachings' };
    const h = flow === 'witness' ? '#/witness' : flow === null ? TAB_HASH[tab] : null;
    if (h && window.location.hash !== h) { try { history.replaceState(null, '', h); } catch (e) {} }
  }, [tab, flow]);

  function startSession(cfg) {
    mtClearActiveSit(); setOrphan(null);   // a new sit supersedes any stale orphan
    mtSaveLastCfg(cfg); setConfig(cfg); setFlow('session');
  }
  function quickStart(practice) {
    const last = mtLoadLastCfg();
    const base = last
      ? { ...last }
      : { duration: 20, practice: 'anapana_vipassana', mode: 'guided', pose: pose, speed: 95, direction: 'down', ambient: true, voice: true, reminderType: 'gong', intervalBell: 0 };
    const pr = practice || base.practice || 'anapana_vipassana';
    startSession({ ...base, practice: pr, mode: pr === 'open' ? 'silent' : 'guided', parked: mtLoadParked() });
  }
  function endSession(res) { setResult(res); setFlow('complete'); }
  // the sitting time is logged either way — "save" additionally keeps
  // the notes, emotion, and body map. Sits under 30 seconds are not
  // logged (aborted before they began).
  function logSession(res, withNotes, dest) {
    if ((res.elapsedSec || 0) >= 30) {
      const actualMin = Math.max(1, Math.round(res.elapsedSec / 60));
      const entry = { id: 'u' + Date.now(), date: new Date().toISOString(), duration: actualMin,
        reflection: withNotes ? res.reflection : undefined, emotion: withNotes ? res.emotion : undefined,
        markers: (withNotes && res.markers) || [] };
      setSessions(prev => [entry, ...prev]);
    }
    setFlow(null); setResult(null); setTab(dest);
  }
  function saveSession(res) { logSession(res, true, 'journal'); }
  // a sit that ended with a closed tab / dead battery — offer to log it
  const [orphan, setOrphan] = useState(() => {
    const o = mtLoadActiveSit();
    return o && o.elapsedSec >= 30 ? o : null;
  });
  function resolveOrphan(log) {
    if (log && orphan) {
      const entry = { id: 'u' + Date.now(), date: new Date(orphan.at || Date.now()).toISOString(),
        duration: Math.max(1, Math.round(orphan.elapsedSec / 60)), markers: [] };
      setSessions(prev => [entry, ...prev]);
    }
    mtClearActiveSit(); setOrphan(null);
  }

  if (!onboarded) return (
    <MTPoseContext.Provider value={pose}>
      <Onboarding vp={vp} onDone={finishOnboard} />
    </MTPoseContext.Provider>
  );

  const render = () => {
  // full-bleed flows
  if (flow === 'witness') return <WitnessScreen vp={vp} audio={audio} onExit={() => setFlow(null)} />;
  if (flow === 'sixtyone') return <SixtyOneScreen vp={vp} audio={audio} onExit={() => setFlow(null)} />;
  if (flow === 'lifetime') return <LifetimeScreen vp={vp} cfg={life} setCfg={setLife} sessions={sessions} onExit={() => setFlow(null)} />;
  if (flow === 'session') return <SessionScreen vp={vp} config={config} audio={audio} onEnd={endSession} />;
  if (flow === 'complete') return (
    <div style={{ position: 'fixed', inset: 0, background: `radial-gradient(120% 80% at 50% 6%, #0a1c2a 0%, ${C.bg} 55%)`, overflow: 'auto' }}>
      <div style={{ maxWidth: 560, margin: '0 auto', paddingTop: 28 }}>
        <CompleteScreen result={result} onSave={saveSession} onDiscard={() => logSession(result, false, 'today')} />
      </div>
    </div>
  );
  if (flow === 'setup') return (
    <div style={{ position: 'fixed', inset: 0, background: `radial-gradient(120% 70% at 50% 0%, #09202e 0%, ${C.bg} 55%)`, overflow: 'auto' }}>
      <div style={{ maxWidth: 560, margin: '0 auto', paddingTop: 26 }}>
        <SetupScreen key={sitPreset ? JSON.stringify(sitPreset) : 'plain'} preset={sitPreset} onStart={startSession} onBack={() => { setSitPreset(null); setFlow(null); }} pose={pose} setPose={setPose} onRelax={() => setFlow('sixtyone')} />
      </div>
    </div>
  );

  // tabbed home
  const wide = vp.isWide;
  return (
    <div style={{ position: 'fixed', inset: 0, background: `radial-gradient(150% 70% at 50% -10%, #0a1722 0%, ${C.bg} 55%)`, color: C.ink, display: 'flex', flexDirection: 'column' }}>
      {wide && <TopNav tab={tab} setTab={setTab} onWitness={() => setFlow('witness')} onBegin={() => setFlow('setup')} />}
      <div style={{ flex: 1, overflow: 'auto', minHeight: 0 }}>
        <div style={{ maxWidth: tab === 'today' ? 620 : 920, margin: '0 auto', width: '100%', boxSizing: 'border-box', paddingTop: wide ? 14 : 'calc(env(safe-area-inset-top, 0px) + 16px)' }}>
          {tab === 'today' && orphan && flow === null && (
            <div style={{ margin: '10px 20px 0', padding: '14px 16px', borderRadius: 16, border: '1px solid rgba(143,208,168,0.4)', background: 'rgba(143,208,168,0.07)' }}>
              <div style={{ fontSize: 14.5, color: MT_COLORS.ink, marginBottom: 10 }}>
                A sitting was interrupted — you were <b>{Math.max(1, Math.round(orphan.elapsedSec / 60))} min</b> in. Log that time?
              </div>
              <div style={{ display: 'flex', gap: 10 }}>
                <MTButton size="sm" tone="ghost" onClick={() => resolveOrphan(true)}>Log it</MTButton>
                <MTButton size="sm" tone="ghost" onClick={() => resolveOrphan(false)}>Let it go</MTButton>
              </div>
            </div>
          )}
          {tab === 'today' && <TodayScreen vp={vp} sessions={sessions} streak={streak} life={life} onBegin={() => setFlow('setup')} onQuickStart={quickStart} onWitness={() => setTab('learn')} onOpenLife={() => setFlow('lifetime')} onTeachings={() => setTab('teachings')} />}
          {tab === 'learn' && <LearnScreen vp={vp} onStartSim={() => setFlow('witness')} onBrowseTeachings={() => setTab('teachings')} />}
          {tab === 'atlas' && <AtlasScreen vp={vp} sessions={sessions} />}
          {tab === 'journal' && <JournalScreen vp={vp} sessions={sessions} />}
          {tab === 'teachings' && <TeachingsScreen vp={vp} />}
        </div>
      </div>
      {!wide && <BottomTabBar tab={tab} setTab={setTab} onWitness={() => setFlow('witness')} />}
    </div>
  );
  };

  return <MTPoseContext.Provider value={pose}>{render()}</MTPoseContext.Provider>;
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
