// app.jsx — Eme root: navigation, timer engine, theming, tweaks
const { useState: uS, useEffect: uE, useRef: uR, useMemo: uM, useCallback: uC } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "defaultDuration": 60,
  "levelView": "auto",
  "animIntensity": 1,
  "accent": ["#7A2BFF", "#2D7CFF"],
  "demoSpeed": "1×"
}/*EDITMODE-END*/;

const SPEED_MAP = { '1×': 1, '8×': 8, '60×': 60 };

function TabBar({ tab, setTab, accent, th, t, dimmed = false }) {
  const tabs = [
    { key: 'focus', tkey: 'nav_focus', Icon: IconFocus },
    { key: 'progress', tkey: 'nav_progress', Icon: IconChart },
    { key: 'settings', tkey: 'nav_settings', Icon: IconGear },
  ];
  return (
    <div style={{
      position: 'absolute', left: 0, right: 0, bottom: 0, zIndex: 40,
      paddingBottom: 26, paddingTop: 10, background: th.tabGrad,
      transition: 'opacity 900ms cubic-bezier(.4,0,.2,1), transform 900ms cubic-bezier(.4,0,.2,1)',
      opacity: dimmed ? 0 : 1, transform: dimmed ? 'translateY(34px)' : 'translateY(0)',
      pointerEvents: dimmed ? 'none' : 'auto',
    }}>
      <div style={{
        margin: '0 16px', borderRadius: 30, padding: '7px 12px 7px', display: 'flex',
        background: 'linear-gradient(180deg, rgba(28,24,40,.78), rgba(13,11,21,.82))',
        border: '1px solid rgba(255,255,255,.1)',
        backdropFilter: 'blur(22px) saturate(140%)', WebkitBackdropFilter: 'blur(22px) saturate(140%)',
        boxShadow: '0 22px 48px rgba(0,0,0,.5), 0 6px 18px rgba(0,0,0,.34), inset 0 1px 0 rgba(255,255,255,.1)',
      }}>
        {tabs.map(({ key, tkey, Icon }) => {
          const on = tab === key;
          return (
            <button key={key} onClick={() => setTab(key)} style={{
              flex: 1, background: 'transparent', border: 'none', cursor: 'pointer',
              display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4, padding: '2px 0',
              color: on ? accent[0] : th.faint,
            }}>
              <span style={{ display: 'flex', filter: on ? `drop-shadow(0 0 7px ${accent[0]}cc)` : 'none' }}><Icon size={28} /></span>
              <span style={{ fontSize: 12.5, letterSpacing: .5, fontWeight: on ? 500 : 400 }}>{t(tkey)}</span>
              <span aria-hidden="true" style={{ marginTop: 2, width: 22, height: 2.5, borderRadius: 2,
                background: on ? accent[0] : 'transparent', boxShadow: on ? `0 0 9px ${accent[0]}` : 'none' }} />
            </button>
          );
        })}
      </div>
    </div>
  );
}

function Toast({ msg, th }) {
  if (!msg) return null;
  return (
    <div style={{
      position: 'absolute', bottom: 120, left: '50%', transform: 'translateX(-50%)', zIndex: 95,
      background: th.modal, border: '1px solid ' + th.border2, color: th.text,
      borderRadius: 14, padding: '12px 20px', fontSize: 14, fontWeight: 300, whiteSpace: 'nowrap',
      backdropFilter: 'blur(10px)', boxShadow: '0 10px 30px rgba(0,0,0,.4)',
    }}>{msg}</div>
  );
}

function LevelUp({ level, anim, th, t, onClose }) {
  return (
    <div className="modal-back" style={{
      position: 'absolute', inset: 0, zIndex: 100,
      background: 'rgba(4,4,8,.82)', backdropFilter: 'blur(8px)',
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 30,
    }}>
      <div style={{ fontSize: 12, letterSpacing: 4, color: level.glow, fontWeight: 500, textTransform: 'uppercase' }}>{t('congrats')}</div>
      <div style={{ margin: '26px 0', position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <LevelRing size={196} colors={[level.left, level.right]} />
        <span style={{ position: 'absolute', fontSize: 46, fontWeight: 500, color: '#fff', textShadow: `0 0 18px ${level.glow}aa` }}>{Math.max(1, LEVELS.findIndex((l) => l.key === level.key) + 1)}</span>
      </div>
      <div style={{ fontSize: 17, color: th.dim, fontWeight: 300 }}>{t('you_reached')}</div>
      <div style={{ fontSize: 40, fontWeight: 200, marginTop: 4, color: th.text }}>{level.name}</div>
      <div style={{ fontSize: 13.5, color: level.glow, marginTop: 10, textAlign: 'center', fontWeight: 400 }}>{t('hours_completed', { n: level.hours.toLocaleString() })}</div>
      <button onClick={onClose} style={{
        marginTop: 34, padding: '14px 40px', borderRadius: 99, border: 'none', cursor: 'pointer',
        background: `linear-gradient(135deg, ${level.left}, ${level.right})`, color: '#fff', fontSize: 15, fontFamily: 'inherit',
        boxShadow: `0 0 24px ${level.glow}88`,
      }}>{t('continue')}</button>
    </div>
  );
}

// Premium goal editor — set a daily/weekly focus target (in minutes).
function GoalEditor({ which, current, th, accent, t, onSave, onClose }) {
  const [val, setVal] = uS(String(current));
  const max = which === 'weekly' ? 4200 : 720;
  const save = () => { const v = parseInt(val, 10); if (v > 0) onSave(Math.max(5, Math.min(max, v))); };
  return (
    <div onClick={onClose} className="goal-editor-back modal-back" style={{
      position: 'absolute', inset: 0, zIndex: 100,
      background: 'rgba(4,4,8,.7)', backdropFilter: 'blur(4px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 32,
    }}>
      <div onClick={(e) => e.stopPropagation()} className="goal-editor-card modal-card" style={{
        background: th.modal, borderRadius: 24, padding: 26, width: '100%', border: '1px solid ' + th.border2,
      }}>
        <div style={{ fontSize: 20, fontWeight: 400, color: th.text }}>{t(which === 'weekly' ? 'weekly_goal' : 'daily_goal')}</div>
        <div style={{ fontSize: 13, color: th.dim, marginTop: 6 }}>{t('any_duration')}</div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 18 }}>
          <input type="number" min="5" max={max} value={val} autoFocus
            onChange={(e) => setVal(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') save(); }}
            style={{ flex: 1, minWidth: 0, padding: '15px 18px', borderRadius: 14, fontSize: 17, fontFamily: 'inherit', background: th.surface, border: '1px solid ' + th.border2, color: th.text, outline: 'none' }} />
          <span style={{ fontSize: 14, color: th.faint }}>{t('minutes_short')}</span>
        </div>
        <div style={{ display: 'flex', gap: 10, marginTop: 22 }}>
          <button onClick={onClose} style={{ flex: 1, padding: '13px', borderRadius: 14, cursor: 'pointer', fontFamily: 'inherit', fontSize: 15, border: '1px solid ' + th.border2, background: 'transparent', color: th.text }}>{t('cancel')}</button>
          <button onClick={save} style={{ flex: 1, padding: '13px', borderRadius: 14, cursor: 'pointer', fontFamily: 'inherit', fontSize: 15, border: 'none', background: `linear-gradient(135deg,${accent[0]},${accent[1]})`, color: '#fff' }}>{t('save')}</button>
        </div>
      </div>
    </div>
  );
}

function ConfirmReset({ th, t, onConfirm, onCancel }) {
  return (
    <div onClick={onCancel} className="modal-back" style={{
      position: 'absolute', inset: 0, zIndex: 100,
      background: 'rgba(4,4,8,.7)', backdropFilter: 'blur(4px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 32,
    }}>
      <div onClick={(e) => e.stopPropagation()} className="modal-card" style={{
        background: th.modal, borderRadius: 24, padding: 26, width: '100%',
        border: '1px solid ' + th.border2, textAlign: 'center',
      }}>
        <div style={{ fontSize: 20, fontWeight: 400, color: th.text }}>{t('reset_q')}</div>
        <div style={{ fontSize: 14, color: th.dim, marginTop: 10, lineHeight: 1.5 }}>
          {t('reset_body')}
        </div>
        <div style={{ display: 'flex', gap: 10, marginTop: 24 }}>
          <button onClick={onCancel} style={{ flex: 1, padding: '13px', borderRadius: 14, cursor: 'pointer', fontFamily: 'inherit', fontSize: 15, border: '1px solid ' + th.border2, background: 'transparent', color: th.text }}>{t('cancel')}</button>
          <button onClick={onConfirm} style={{ flex: 1, padding: '13px', borderRadius: 14, cursor: 'pointer', fontFamily: 'inherit', fontSize: 15, border: 'none', background: 'linear-gradient(135deg,#F23F5B,#B01446)', color: '#fff' }}>{t('reset')}</button>
        </div>
      </div>
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const { state, addFocusMinutes, deleteSession, clearHistory, recordShare, updateSettings, resetProgress, setPremium, updateGoals, saveFavDuration, removeFavDuration, markReviewAsked, addQuote, updateQuote, removeQuote, createPairingCode, linkWithCode,
    setActiveTag, addTag, renameTag, removeTag,
    setShieldEnabled, setShieldMode, setShieldCustom,
    useFreezeToken, setAutoProtect, grantMonthlyFreeze, updateWidgets } = useEmeState();
  // Refill the monthly free streak-freeze token (1/month) once at app start.
  uE(() => { grantMonthlyFreeze(); }, []);
  const [tab, setTab] = uS('focus');
  const [phase, setPhase] = uS('splash'); // splash | onboarding | app
  const [enterNonce, setEnterNonce] = uS(0); // bumps when entering Focus, to replay the sphere reveal
  const pending = uR({});
  const isPremium = !!state.settings.premium;

  uE(() => { if (phase === 'app') setEnterNonce((n) => n + 1); }, [phase]);

  // ── theme (dark only) ──
  const th = uM(() => makeTheme(), []);
  uE(() => { document.documentElement.dataset.theme = 'dark'; }, []);

  // ── haptics — install the global tap listener once; keep it in sync with the setting ──
  uE(() => { installGlobalHaptics(); }, []);
  uE(() => { setHapticsEnabled(state.settings.haptics !== false); }, [state.settings.haptics]);
  uE(() => { setSoundsEnabled(state.settings.sounds !== false); }, [state.settings.sounds]);

  // ── language / direction ──
  const i18n = uM(() => makeT(state.settings.language), [state.settings.language]);
  uE(() => {
    document.documentElement.dir = i18n.dir;
    document.documentElement.lang = i18n.lang;
  }, [i18n.dir, i18n.lang]);

  // ── derived level ──
  const totalHours = state.totalMinutes / 60;
  const autoIdx = levelForHours(totalHours);
  const levelIdx = t.levelView === 'auto' ? autoIdx : Math.max(0, LEVELS.findIndex((l) => l.key === t.levelView));
  const level = LEVELS[levelIdx];
  const nextHours = nextThreshold(levelIdx);
  const accent = Array.isArray(t.accent) ? t.accent : [t.accent, t.accent];
  const anim = t.animIntensity;
  const speed = SPEED_MAP[t.demoSpeed] || 1;

  // ── timer ──
  // Three free modes selected by tapping the timer: standard (countdown), countup
  // (counts up), pomodoro (work/break × 4). Durations are customizable on Premium.
  const POMO_CYCLES = 4;
  const mode = state.settings.timerMode || 'standard';
  const POMO_FOCUS = Math.max(1, state.settings.pomoWork || 25);
  const POMO_BREAK = Math.max(1, state.settings.pomoBreak || 5);
  const cuGoal = state.settings.cuGoal || 0;     // count-up goal in minutes (0 = no limit)
  const modeDur = mode === 'pomodoro' ? POMO_FOCUS : (state.settings.stdDuration || 25);

  const [durationMin, setDurationMin] = uS(modeDur);
  const [remaining, setRemaining] = uS(modeDur * 60);
  const [elapsed, setElapsed] = uS(0);          // seconds elapsed this session (count-up)
  const [status, setStatus] = uS('idle');       // idle | running | paused
  const [pomoPhase, setPomoPhase] = uS('focus'); // focus | break
  const [pomoCycle, setPomoCycle] = uS(1);       // 1..POMO_CYCLES
  const completing = uR(false);
  const creditSec = uR(0);                       // focus seconds to credit on completion
  const pausedRef = uR(false);                   // did the user pause at any point this session
  const sessionStartRef = uR(0);                 // wall-clock start of the current session (for history)
  const pomoPhaseRef = uR('focus'); pomoPhaseRef.current = pomoPhase;

  uE(() => {
    if (status === 'idle') { setDurationMin(modeDur); setRemaining(modeDur * 60); }
  }, [modeDur, status]);

  uE(() => {
    if (status !== 'running') return;
    const id = setInterval(() => {
      setElapsed((e) => e + speed);
      if (mode !== 'countup') setRemaining((r) => Math.max(0, r - speed));
      // credit focus time — everything except a pomodoro break
      if (!(mode === 'pomodoro' && pomoPhaseRef.current === 'break')) creditSec.current += speed;
    }, 1000);
    return () => clearInterval(id);
  }, [status, speed, mode]);

  const doComplete = uC(() => {
    if (completing.current) return;
    completing.current = true;
    const min = Math.max(1, Math.round(creditSec.current / 60));  // actual focus minutes, all modes
    const before = state.totalMinutes / 60;
    const after = (state.totalMinutes + min) / 60;
    if (state.settings.sounds) ambientEngine.cue('complete', level);
    haptic(HAPTIC.success);     // session complete — celebratory
    const leveled = t.levelView === 'auto' && levelForHours(after) > levelForHours(before);
    const willCount = (state.sessionsSinceInstall || 0) + 1;
    // Achievement banners are driven by the achUnlocked watcher (see App), not by hours.
    pending.current = {
      levelIdx: leveled ? levelForHours(after) : -1,
      achTitle: null,
      review: willCount > 0 && willCount % 10 === 0 && !state.reviewAsked,
    };
    if (leveled && state.settings.sounds) setTimeout(() => ambientEngine.cue('level', LEVELS[pending.current.levelIdx]), 620);
    setCompleteData({ sessionMin: min, todayMin: state.todayMinutes + min, mode, ts: Date.now() });
    addFocusMinutes(min, { mode, paused: pausedRef.current, sound: activeSound, tag: state.activeTag,
      startTs: sessionStartRef.current || undefined, endTs: Date.now() });
    try { if (window.EmeoShield) window.EmeoShield.disengage(); } catch (e) {} // Focus Shield off
    setStatus('idle');
    setElapsed(0); setPomoPhase('focus'); setPomoCycle(1);
    setRemaining(durationMin * 60);
    creditSec.current = 0;
    setTimeout(() => { completing.current = false; }, 800);
  }, [state.totalMinutes, state.todayMinutes, state.sessionsSinceInstall, state.reviewAsked, durationMin, level, state.settings, addFocusMinutes, t.levelView, i18n]);

  const closeComplete = uC(() => {
    setCompleteData(null);
    const p = pending.current || {};
    if (p.levelIdx >= 0) {
      setLevelUpIdx(p.levelIdx);
      haptic([0, 60, 50, 60], state.settings.haptics);
    } else if (p.achTitle) {
      setAchBanner(p.achTitle);
      haptic([0, 45, 40, 45], state.settings.haptics);
    }
    if (p.review) {
      setTimeout(() => setShowReview(true), p.levelIdx >= 0 ? 2600 : (p.achTitle ? 3600 : 500));
    }
    pending.current = {};
  }, [state.settings.haptics, markReviewAsked]);

  // phase / completion management when a countdown phase reaches zero
  uE(() => {
    if (status !== 'running' || mode === 'countup' || remaining > 0) return;
    if (mode === 'pomodoro') {
      if (pomoPhase === 'focus') {
        if (pomoCycle >= POMO_CYCLES) { doComplete(); }
        else {
          if (state.settings.sounds) ambientEngine.cue('pause');   // gentle into-break chime
          haptic([0, 30], state.settings.haptics);
          setPomoPhase('break'); setRemaining(POMO_BREAK * 60);
        }
      } else { // break finished → next focus cycle
        if (state.settings.sounds) ambientEngine.cue('resume');
        haptic([0, 30], state.settings.haptics);
        setPomoCycle((c) => c + 1); setPomoPhase('focus'); setRemaining(POMO_FOCUS * 60);
      }
    } else {
      doComplete();   // standard
    }
  }, [remaining, status, mode, pomoPhase, pomoCycle]);

  // count-up goal — completes when the elapsed time reaches a (premium) goal
  uE(() => {
    if (status === 'running' && mode === 'countup' && cuGoal > 0 && elapsed >= cuGoal * 60) doComplete();
  }, [elapsed, status, mode, cuGoal]);

  // ── sounds ──
  const [activeSound, setActiveSound] = uS(null);
  // Preload the small free core sounds at launch for instant playback; the larger
  // premium files decode lazily on first play to keep startup memory/battery low.
  uE(() => { try { whiteNoisePlayer.preloadAll(SOUNDS.filter((s) => s.free && s.file)); } catch (e) {} }, []);
  const [volume, setVolume] = uS(0.6);
  const selectSound = uC((key) => {
    const snd = SOUNDS.find((s) => s.key === key);
    // Ambient sounds are Premium (Rain is free) — locked taps open the paywall.
    if (snd && !snd.free && !isPremium) { openPremium(); return; }
    if (activeSound === key) { try { whiteNoisePlayer.stop(); } catch (e) {} setActiveSound(null); haptic(HAPTIC.light); }      // deselect
    else { try { whiteNoisePlayer.setVolume(volume); whiteNoisePlayer.play(key, snd && snd.file); } catch (e) {} setActiveSound(key); haptic(HAPTIC.medium); } // select
  }, [activeSound, volume, isPremium]);
  const changeVolume = uC((v) => { setVolume(v); whiteNoisePlayer.setVolume(v); }, []);

  // ── in-session quote pool ──
  // Free users see Eme's curated phrases; premium users choose Eme / their own / both.
  const quotesOn = state.settings.quotes !== false;
  const quoteSource = state.settings.quoteSource || 'mix';
  const quotePool = uM(
    () => resolveQuotePool({ system: i18n.phrases, custom: state.quotes, source: quoteSource, isPremium }),
    [i18n.phrases, state.quotes, quoteSource, isPremium]
  );

  // ── overlays ──
  const [showNature, setShowNature] = uS(false);
  const [showDuration, setShowDuration] = uS(false);
  const [showModes, setShowModes] = uS(false);
  const [levelUpIdx, setLevelUpIdx] = uS(-1);
  const [showReset, setShowReset] = uS(false);
  const [showPremium, setShowPremium] = uS(false);
  const [showQuotes, setShowQuotes] = uS(false);
  const [goalEdit, setGoalEdit] = uS(null);
  const [completeData, setCompleteData] = uS(null);
  const [achBanner, setAchBanner] = uS(null);
  // Watch achUnlocked for freshly-stamped achievements and slide a celebration banner for
  // the latest one (respecting the Achievement Animations setting). Skips the initial mount.
  const achSeen = uR(null);
  uE(() => {
    const keys = Object.keys(state.achUnlocked || {});
    if (achSeen.current === null) { achSeen.current = new Set(keys); return; }
    const fresh = keys.filter((k) => !achSeen.current.has(k));
    achSeen.current = new Set(keys);
    if (fresh.length && state.settings.achievementAnims !== false) {
      const a = ACHIEVEMENTS.find((x) => x.key === fresh[fresh.length - 1]);
      if (a) {
        setAchBanner(a.title);
        if (state.settings.sounds) ambientEngine.cue('achievement');
        haptic([0, 45, 40, 45], state.settings.haptics);
      }
    }
  }, [state.achUnlocked]);
  const [showShare, setShowShare] = uS(false);
  const [showSync, setShowSync] = uS(false);
  const [showHistory, setShowHistory] = uS(false);
  const [showShield, setShowShield] = uS(false);   // Focus Shield config sheet
  const [showWidgets, setShowWidgets] = uS(false);  // Widgets & Live Activities config sheet
  const [pendingStart, setPendingStart] = uS(false); // onboarding → auto-start first session
  const [shareKind, setShareKind] = uS('rank');   // which Sharing achievement a share counts toward
  const [showJourney, setShowJourney] = uS(false);
  const [showAchievements, setShowAchievements] = uS(false);
  const [showReview, setShowReview] = uS(false);
  const [toast, setToast] = uS(null);
  const toastTimer = uR(null);
  const showToast = uC((m) => {
    setToast(m);
    clearTimeout(toastTimer.current);
    toastTimer.current = setTimeout(() => setToast(null), 2200);
  }, []);

  // ── premium gate ──
  const openPremium = uC(() => {
    setShowNature(false); setShowDuration(false);
    setShowPremium(true);
    haptic(HAPTIC.warning);   // hitting a locked / paywalled feature
  }, []);
  const subscribe = uC((plan) => {
    // On a native build with RevenueCat, hand off to the store purchase flow for the chosen
    // plan ('year' | 'month'); the entitlement comes back via the native→web bridge (below).
    if (window.EmeoBilling && window.EmeoBilling.available() && window.EmeoBilling.purchase(plan)) {
      haptic(HAPTIC.medium);
      return;
    }
    // Fallback (browser preview / Expo Go without RevenueCat): simulate the unlock.
    setPremium(true);
    if (state.settings.sounds) ambientEngine.cue('achievement');
    haptic(HAPTIC.success);   // Premium purchased — celebratory
    setShowPremium(false);
    showToast(i18n.t('toast_subscribed'));
  }, [setPremium, state.settings.sounds, state.settings.haptics, i18n, showToast]);
  const restorePurchases = uC(() => {
    if (window.EmeoBilling && window.EmeoBilling.available() && window.EmeoBilling.restore()) {
      haptic(HAPTIC.medium); showToast(i18n.t('toast_restoring'));
    }
  }, [i18n, showToast]);
  // Native → web entitlement sync: when RevenueCat reports Premium active/inactive, mirror
  // it into app state. No-op in the browser preview / Expo Go (the bridge never fires).
  uE(() => {
    if (!window.EmeoBilling) return;
    const cur = window.EmeoBilling.isPremium();
    if (cur === true) setPremium(true);
    return window.EmeoBilling.onChange((p) => {
      setPremium(p);
      if (p) {
        setShowPremium(false);
        if (state.settings.sounds) ambientEngine.cue('achievement');
        haptic(HAPTIC.success);
        showToast(i18n.t('toast_subscribed'));
      }
    });
  }, []);

  // ── timer actions ──
  const resetTimer = () => { setElapsed(0); setPomoPhase('focus'); setPomoCycle(1); creditSec.current = 0; };
  const onStart = () => {
    resetTimer();
    pausedRef.current = false;   // fresh session — no pause yet (no-pause challenge tracking)
    sessionStartRef.current = Date.now();
    // Focus Shield: engage app/website blocking (no-op on web). "all" mode uses the curated
    // distraction list; "custom" uses the user's own sites/apps.
    try {
      const fs = state.focusShield;
      if (fs && fs.enabled && window.EmeoShield) {
        const preset = fs.mode === 'custom'
          ? { all: false, sites: fs.sites || [], apps: fs.apps || [] }
          : { all: true, sites: (window.SHIELD_ALL_SITES || []), apps: [] };
        window.EmeoShield.engage(preset);
      }
    } catch (e) {}
    if (mode === 'pomodoro') setRemaining(POMO_FOCUS * 60);
    else if (mode !== 'countup') setRemaining(durationMin * 60);
    setStatus('running');
    if (state.settings.sounds) ambientEngine.cue('start', level);
    haptic(HAPTIC.heavy);   // starting a session — strongest tap
  };
  const onPause = () => { pausedRef.current = true; setStatus('paused'); if (state.settings.sounds) ambientEngine.cue('pause'); haptic(HAPTIC.medium); };
  const onResume = () => { setStatus('running'); if (state.settings.sounds) ambientEngine.cue('resume'); haptic(HAPTIC.medium); };
  const onStop = () => {
    const wasActive = status !== 'idle';
    // Count Up: stopping a meaningful session is its completion (credit + screen).
    if (mode === 'countup' && wasActive && elapsed >= 60) { doComplete(); return; }
    if (wasActive && state.settings.sounds) ambientEngine.cue('stop');
    if (wasActive) haptic(HAPTIC.medium);
    try { if (window.EmeoShield) window.EmeoShield.disengage(); } catch (e) {} // Focus Shield off
    setStatus('idle'); setRemaining(durationMin * 60); resetTimer();
  };
  const onSphereTap = () => {};

  // Onboarding "Start Session": once we're on the app with the chosen mode applied, launch it.
  // `durationMin` is synced from settings one effect-cycle late, so we defer + re-arm on every
  // durationMin change; the timeout only fires once the duration has settled to the chosen value.
  uE(() => {
    if (phase === 'app' && pendingStart) {
      const id = setTimeout(() => { setPendingStart(false); onStart(); }, 140);
      return () => clearTimeout(id);
    }
  }, [phase, pendingStart, state.settings.timerMode, durationMin]);

  // Keep the OS media controls (Android notification / lock screen) in sync with the
  // active ambient sound + session — title = sound, subtitle = mode + status.
  uE(() => {
    const running = status === 'running', paused = status === 'paused';
    const sessionActive = running || paused;
    if (!sessionActive && !activeSound) { setMediaSession({ clear: true }); return; }
    const snd = activeSound ? SOUNDS.find((s) => s.key === activeSound) : null;
    const soundName = snd ? i18n.t(snd.t) : null;
    let statusLine = '';
    if (sessionActive) {
      const base = mode === 'pomodoro' ? 'pomodoro' : mode === 'countup' ? 'tracking' : 'focus';
      statusLine = i18n.t(base + (running ? '_in_progress' : '_paused'));
    } else if (soundName) {
      statusLine = i18n.t('white_noise');
    }
    setMediaSession({
      title: soundName || i18n.t('focus_in_progress'),
      artist: statusLine,
      playing: running || (!sessionActive && !!activeSound),
      onPlay: () => { if (paused) onResume(); },
      onPause: () => { if (running) onPause(); },
      onStop: () => { try { whiteNoisePlayer.stop(); } catch (e) {} setActiveSound(null); if (sessionActive) onStop(); },
    });
  }, [status, mode, activeSound, i18n.lang]);

  // Selecting a timer mode is free; the sheet stays open so durations can be tuned.
  const selectMode = uC((m) => {
    if (m !== mode) { updateSettings({ timerMode: m }); haptic(HAPTIC.medium); }
  }, [mode, updateSettings, state.settings.haptics]);

  // ── timer view (mode-aware) ──
  const pomoTotal = (pomoPhase === 'focus' ? POMO_FOCUS : POMO_BREAK) * 60;
  const view_total = mode === 'pomodoro' ? pomoTotal : Math.max(1, durationMin * 60);
  const view_display = mode === 'countup' ? elapsed
    : (status === 'idle' && mode === 'pomodoro') ? POMO_FOCUS * 60
    : remaining;
  const mmss = fmtClock(view_display);
  const view_progress = (status === 'idle' || mode === 'countup') ? 0 : Math.min(1, 1 - remaining / view_total);
  const view_rate = mode === 'countup' ? 0 : (view_total > 0 ? speed / view_total : 0);

  // ── fit-to-viewport scaling ──
  const wrapRef = uR(null);
  uE(() => {
    const fit = () => {
      if (!wrapRef.current) return;
      const s = Math.min(1, (window.innerWidth - 32) / 402, (window.innerHeight - 32) / 874);
      wrapRef.current.style.transform = `scale(${s})`;
    };
    fit();
    window.addEventListener('resize', fit);
    return () => window.removeEventListener('resize', fit);
  }, []);

  return (
    <I18nCtx.Provider value={i18n}>
    <ThemeCtx.Provider value={th}>
    <div ref={wrapRef} dir={i18n.dir} style={{ transformOrigin: 'center center', fontFamily: i18n.lang === 'ar' ? "'Noto Sans Arabic','Sora',system-ui,sans-serif" : undefined }}>
      <IOSDevice dark width={402} height={874}>
        <div style={{ position: 'absolute', inset: 0, background: th.bg }} />
        <div style={{ position: 'absolute', inset: 0, overflow: 'hidden' }}>
          {tab === 'focus' && (
            <div className="tabfade" style={{ height: '100%', paddingBottom: 92, boxSizing: 'border-box' }}>
              <FocusScreen
                level={level} accent={accent} anim={anim} th={th}
                totalHours={totalHours} nextHours={nextHours}
                timer={{ mmss, status, progress: view_progress, rate: view_rate,
                  mode, pomoPhase, pomoCycle, pomoCycles: POMO_CYCLES }}
                quotePool={quotePool} quotesOn={quotesOn}
                soundActive={!!activeSound} activeSoundKey={activeSound}
                onStopSound={() => { try { whiteNoisePlayer.stop(); } catch (e) {} setActiveSound(null); }} settings={state.settings}
                onStart={onStart} onPause={onPause} onResume={onResume} onStop={onStop}
                onSphereTap={onSphereTap}
                onOpenNature={() => { setShowModes(false); setShowNature(true); }}
                onOpenModes={() => { setShowNature(false); setShowModes(true); }}
                reveal={enterNonce}
              />
            </div>
          )}
          {tab === 'progress' && (
            <div className="tabfade" style={{ height: '100%' }}>
              <ProgressScreen level={level} levelIdx={levelIdx} accent={accent} th={th}
                totalHours={totalHours} nextHours={nextHours}
                stats={state} unlocked={totalHours}
                onUseFreeze={useFreezeToken} onAutoProtect={setAutoProtect}
                isPremium={isPremium} onLocked={openPremium} onEditGoals={(which) => setGoalEdit(which)}
                onShare={() => { setShareKind('rank'); setShowShare(true); haptic(12, state.settings.haptics); }}
                onOpenJourney={() => setShowJourney(true)}
                onOpenAchievements={() => setShowAchievements(true)} />
            </div>
          )}
          {tab === 'settings' && (
            <div className="tabfade" style={{ height: '100%' }}>
              <SettingsScreen accent={accent} th={th} settings={state.settings} updateSettings={updateSettings}
                onReset={() => setShowReset(true)} onToast={showToast}
                isPremium={isPremium} onLocked={openPremium}
                onOpenQuotes={() => setShowQuotes(true)}
                onOpenSync={() => setShowSync(true)}
                onOpenHistory={() => setShowHistory(true)}
                focusShield={state.focusShield} widgets={state.widgets}
                onOpenShield={() => setShowShield(true)} onOpenWidgets={() => setShowWidgets(true)}
                onOpenShare={() => { setShareKind('stats'); setShowShare(true); }} />
            </div>
          )}
        </div>

        <TabBar tab={tab} setTab={setTab} accent={accent} th={th} t={i18n.t} dimmed={tab === 'focus' && status !== 'idle'} />

        {showNature && <NatureSheet onClose={() => setShowNature(false)} accent={accent} th={th}
          activeSound={activeSound} onSelect={selectSound} volume={volume} onVolume={changeVolume}
          isPremium={isPremium} onLocked={openPremium} />}
        {showModes && <ModeSheet onClose={() => setShowModes(false)} accent={accent} th={th}
          currentMode={mode} onSelect={selectMode} settings={state.settings} updateSettings={updateSettings}
          isPremium={isPremium} onLocked={openPremium} onStart={() => { setShowModes(false); onStart(); }}
          tags={state.tags} activeTag={state.activeTag} onSelectTag={setActiveTag} onAddTag={addTag} onRemoveTag={removeTag}
          favDurations={state.favDurations} onSaveFav={saveFavDuration} onRemoveFav={removeFavDuration}
          focusShield={state.focusShield} onToggleShield={setShieldEnabled} />}
        {levelUpIdx >= 0 && <LevelUp level={LEVELS[levelUpIdx]} anim={anim} th={th} t={i18n.t} onClose={() => setLevelUpIdx(-1)} />}
        {showReset && <ConfirmReset th={th} t={i18n.t}
          onConfirm={() => { resetProgress(); setShowReset(false); setTab('progress'); haptic([0, 40, 30], state.settings.haptics); showToast(i18n.t('toast_progress_reset')); }}
          onCancel={() => setShowReset(false)} />}
        {goalEdit && <GoalEditor which={goalEdit} current={state.goals[goalEdit]} th={th} accent={accent} t={i18n.t}
          onSave={(v) => { updateGoals({ [goalEdit]: v }); setGoalEdit(null); haptic(14, state.settings.haptics); }}
          onClose={() => setGoalEdit(null)} />}
        {showPremium && <PremiumPage th={th} accent={accent} level={level} isPremium={isPremium}
          onClose={() => setShowPremium(false)} onSubscribe={subscribe} onRestore={restorePurchases} />}
        {showQuotes && isPremium && <QuotesManager th={th} accent={accent} level={level}
          quotes={state.quotes} source={quoteSource}
          onSetSource={(v) => updateSettings({ quoteSource: v })}
          onAdd={addQuote} onUpdate={updateQuote} onDelete={removeQuote}
          onToast={showToast} onClose={() => setShowQuotes(false)} />}
        {completeData && <SessionComplete level={level} anim={anim} th={th} accent={accent}
          sessionMin={completeData.sessionMin} todayMin={completeData.todayMin}
          mode={completeData.mode} ts={completeData.ts} onShared={() => recordShare('session')} onToast={showToast}
          settings={state.settings} onClose={closeComplete} />}
        {achBanner && <AchievementBanner th={th} accent={accent} title={achBanner} onDone={() => setAchBanner(null)} />}
        {showShare && <ShareCard th={th} accent={accent} level={level} stats={state} totalHours={totalHours}
          kind={shareKind} onShared={recordShare}
          onClose={() => setShowShare(false)} onToast={showToast} />}
        {showSync && <PairingSheet th={th} accent={accent}
          createPairingCode={createPairingCode} linkWithCode={linkWithCode}
          onClose={() => setShowSync(false)} />}
        {showHistory && <SessionHistory stats={state} th={th} accent={accent}
          onDeleteSession={deleteSession} onClearHistory={clearHistory}
          onShared={() => recordShare('session')} onToast={showToast}
          onClose={() => setShowHistory(false)} />}
        {showShield && <ShieldSheet th={th} accent={accent} focusShield={state.focusShield}
          isPremium={isPremium} onLocked={openPremium}
          onToggle={setShieldEnabled} onSetMode={setShieldMode} onSetCustom={setShieldCustom}
          onClose={() => setShowShield(false)} onToast={showToast} />}
        {showWidgets && <WidgetsSheet th={th} accent={accent} widgets={state.widgets}
          isPremium={isPremium} onLocked={openPremium} onUpdate={updateWidgets}
          onClose={() => setShowWidgets(false)} />}
        {showJourney && <RankJourney levelIdx={levelIdx} totalHours={totalHours} th={th} accent={accent}
          onClose={() => setShowJourney(false)} />}
        {showAchievements && <AchievementsJourney stats={state} th={th} accent={accent}
          onClose={() => setShowAchievements(false)} />}
        {showReview && <ReviewPrompt th={th} accent={accent}
          onRate={() => { markReviewAsked(); setShowReview(false); showToast(i18n.t('toast_review_thanks')); }}
          onNever={() => { markReviewAsked(); setShowReview(false); }}
          onClose={() => setShowReview(false)} />}
        {phase === 'splash' && <Splash onDone={() => setPhase(onboardingSeen() ? 'app' : 'onboarding')} />}
        {phase === 'onboarding' && <Onboarding th={th} accent={accent}
          onComplete={(res) => {
            // Personalize from the answers (capacity → default duration, activity → tag, mode).
            const patch = {};
            if (res.capacity) patch.stdDuration = res.capacity === '<15' ? 15 : res.capacity === '15-30' ? 30 : res.capacity === '30-60' ? 45 : 60;
            if (res.mode === 'pomo') { patch.timerMode = 'pomodoro'; patch.pomoWork = 25; patch.pomoBreak = 5; }
            else if (res.mode === 'deep') { patch.timerMode = 'standard'; patch.stdDuration = 50; }
            else if (res.mode === 'countup') { patch.timerMode = 'countup'; }
            if (Object.keys(patch).length) updateSettings(patch);
            const tagMap = { work: 'work', studies: 'study', reading: 'reading' };
            if (res.activity && tagMap[res.activity]) setActiveTag(tagMap[res.activity]);
            else if (res.activity === 'projects') addTag('Projects', '#FF8A5C'); // addTag sets it active
            markOnboarded(); setPhase('app'); setTab('focus');
            if (res.start) setPendingStart(true);
          }} />}
        <Toast msg={toast} th={th} />
      </IOSDevice>

      <TweaksPanel>
        <TweakSection label="Session" />
        <TweakRadio label="Default length" value={t.defaultDuration} options={[30, 60, 120]}
          onChange={(v) => setTweak('defaultDuration', parseInt(v, 10))} />
        <TweakSelect label="Demo speed" value={t.demoSpeed} options={['1×', '8×', '60×']}
          onChange={(v) => setTweak('demoSpeed', v)} />
        <TweakSection label="Ring & Level" />
        <TweakSelect label="Level view" value={t.levelView}
          options={[{ value: 'auto', label: 'Auto (by hours)' }, ...LEVELS.map((l) => ({ value: l.key, label: l.name }))]}
          onChange={(v) => setTweak('levelView', v)} />
        <TweakSlider label="Animation intensity" value={t.animIntensity} min={0.4} max={1.6} step={0.1}
          onChange={(v) => setTweak('animIntensity', v)} />
        <TweakSection label="Brand accent" />
        <TweakColor label="Accent palette" value={t.accent}
          options={[['#7A2BFF', '#2D7CFF'], ['#B57BFF', '#56D8FF'], ['#56D8FF', '#7BFFB5'], ['#FF8A5B', '#FF5B8A'], ['#FFD98A', '#FF8AD0']]}
          onChange={(v) => setTweak('accent', v)} />
        <TweakSection label="Account" />
        <TweakToggle label="Premium unlocked" value={isPremium} onChange={(v) => setPremium(v)} />
      </TweaksPanel>
    </div>
    </ThemeCtx.Provider>
    </I18nCtx.Provider>
  );
}

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

  