// Main app shell: router between Home/Flow/Progress/Settings + overlays
// for Player, PresetDetail, Complete. Stateless about frame — wrapped by
// device frames (iOS, Android) and desktop view.

function BFApp({ accentKey = 'aqua', setAccentKey, darkMode = true, setDarkMode, vizStyle = 'circle', setVizStyle, voice = true, setVoice, voicePack = 'en-drew', setVoicePack, ttsVoiceName = null, setTtsVoiceName, uiLocale = 'en', setUiLocale, sound = true, setSound, ambientId = 'silence', setAmbientId, ambientVol = 0.35, setAmbientVol, haptics = true, setHaptics, signedIn = false, setSignedIn, user = null, resetOnboarded, fontPairing = 'serif', setFontPairing, initialTab = 'home', compactTabs = false }) {
  const [tab, setTab] = React.useState(initialTab);
  const [preset, setPreset] = React.useState(null);
  const [active, setActive] = React.useState(null);
  const [complete, setComplete] = React.useState(null);
  const [helpOpen, setHelpOpen] = React.useState(false);
  const accentH = BF_ACCENTS[accentKey].h;
  const theme = darkMode ? BF_THEMES.dark : BF_THEMES.light;
  // Broadcast current theme + accent so vanilla-JS modules (BFDialog, BFToast,
  // BFFeedback, etc.) that build DOM directly can pick up light/dark colors
  // without threading a React prop through. Read via window.bfCurrentTheme.
  React.useEffect(() => {
    window.bfCurrentTheme = theme;
    window.bfCurrentAccentH = accentH;
  }, [theme, accentH]);
  React.useEffect(() => { bfSetFontPairing(fontPairing); }, [fontPairing]);

  // ── Active profile + Kids mode derivation ─────────────────────────
  // BFProfiles is a stub here: it returns one flavor-derived profile, so
  // the kids deploy gets kid semantics (audience filter, PIN on Settings)
  // and the adult deploy gets adult semantics. No switching at runtime.
  const activeProfile = React.useMemo(() => {
    try { return (window.BFProfiles && window.BFProfiles.current()) || null; }
    catch (e) { return null; }
  }, []);
  const kidsMode = !!(activeProfile && activeProfile.kind === 'kid');

  // PIN gate on Settings in the kids flavor. Tapping the Settings tab runs
  // the parent PIN prompt first; cancelling leaves the tab alone.
  const guardedSetTab = React.useCallback(async (next) => {
    if (next === tab) return;
    if (kidsMode && next === 'settings' && window.BFKids) {
      const ok = await window.BFKids.requirePin({ reason: 'settings' });
      if (!ok) return;
    }
    setTab(next);
  }, [tab, kidsMode]);

  // Real session stats — loaded from BFSessions on mount, refreshed after
  // every completed session. Seed with a zeroed object so the first render
  // doesn't flash NaN/undefined before the async load lands.
  const ZERO_STATS = {
    streak: 0, totalSessions: 0, totalMinutes: 0, totalMinutesAllTime: 0,
    totalCycles: 0, weekMinutes: [0, 0, 0, 0, 0, 0, 0],
    distinctPatterns: 0, hasDawnSession: false, hasNightSession: false,
  };
  const [session, setSession] = React.useState(ZERO_STATS);
  const refreshStats = React.useCallback(async () => {
    try {
      const s = await BFSessions.stats();
      setSession(s);
    } catch (e) { /* keep last known good */ }
  }, []);
  React.useEffect(() => { refreshStats(); }, [refreshStats]);

  // Ladder unlock state — drives Home/Library lock badges. Re-renders on
  // every BFUnlock change (session counted, admin config landing).
  const [unlock, setUnlock] = React.useState(() => {
    try { return window.BFUnlock ? window.BFUnlock.snapshot() : null; }
    catch (e) { return null; }
  });
  React.useEffect(() => {
    if (!window.BFUnlock) return;
    const off = window.BFUnlock.subscribe(setUnlock);
    window.BFUnlock.load().then(setUnlock).catch(() => {});
    return off;
  }, []);
  // One breath = no library. The Flow tab appears once a second breath has
  // been earned; bounce off it if it disappears (e.g. admin shrinks the
  // ladder under the user's feet).
  const showFlow = !!(unlock && unlock.unlocked && unlock.unlocked.length > 1);
  React.useEffect(() => {
    if (!showFlow && tab === 'flow') setTab('home');
  }, [showFlow, tab]);

  const startSession = (pattern, duration) => {
    // Hard safety: never launch an adult pattern in the kids flavor, even
    // from stale state or a deep link. UI already filters; last line of
    // defence.
    if (typeof bfPatternAllowedForProfile === 'function'
        && !bfPatternAllowedForProfile(pattern, activeProfile)) {
      try { BFToast.error('Not available', 'This pattern isn’t available in this app.'); } catch (e) {}
      return;
    }
    // Ladder gate: locked patterns can't start. (UI shows them locked;
    // this guards deep links and stale preset sheets.)
    if (window.BFUnlock && !window.BFUnlock.isUnlocked(pattern.id)) {
      try { BFToast.info('Locked', 'Keep your daily streak going to unlock this breath.'); } catch (e) {}
      return;
    }
    setPreset(null);
    setActive({ pattern, duration });
  };

  const onComplete = async () => {
    const done = active;
    const cycles = Math.floor(done.duration / bfCycleSeconds(done.pattern));
    setComplete({ pattern: done.pattern, duration: done.duration, cycles });
    setActive(null);
    // Persist the session, count today toward the ladder, then surface
    // celebrations: ladder unlock first (the headline event), then streak
    // tiers. Milestones still evaluate inside BFSessions.complete but have
    // no UI in this app.
    try {
      const res = await BFSessions.complete({
        patternId:   done.pattern.id,
        patternName: done.pattern.name,
        durationSec: done.duration,
        cycles,
      });
      setSession(res.stats);
      const queue = [];
      const i18n = window.BFI18n;
      if (window.BFUnlock) {
        const r = await window.BFUnlock.recordPracticeDay();
        if (r.unlockedNow) {
          const p = (typeof BF_PATTERNS !== 'undefined' ? BF_PATTERNS : []).find((x) => x.id === r.unlockedNow);
          const pname = p ? (typeof bfPatternName === 'function' ? bfPatternName(p) : p.name) : r.unlockedNow;
          const title = (i18n && i18n.t('unlock.new_breath_title')) || 'New breath unlocked';
          queue.push({ title: title === 'unlock.new_breath_title' ? 'New breath unlocked' : title, body: pname });
        }
      }
      (res.newlyUnlockedTiers || []).forEach((tier) => {
        const tierName = bfTierName(tier);
        const tierDesc = bfTierDesc(tier);
        const title = (i18n && i18n.t('app.tier_reached_title', { tier: tierName })) || `${tierName} tier reached`;
        queue.push({ title, body: tierDesc });
      });
      queue.forEach((toast, i) => {
        setTimeout(() => {
          try { BFToast.success(toast.title, toast.body); } catch (e) {}
        }, 600 + i * 900);
      });
    } catch (e) { /* storage error — ignore, UI still works */ }
  };

  return (
    <div style={{ position: 'absolute', inset: 0, background: theme.bg, overflow: 'hidden', display: 'flex', flexDirection: compactTabs ? 'row' : 'column' }}>
      {compactTabs && <div style={{ width: 180, flexShrink: 0 }}><BFTabBar tab={tab === 'admin' ? 'settings' : tab} setTab={guardedSetTab} theme={theme} accentH={accentH} compact kidsMode={kidsMode} showFlow={showFlow} /></div>}
      <div style={{ flex: 1, position: 'relative', overflow: 'auto' }}>
        {tab === 'home' && <BFHome theme={theme} accentH={accentH} session={session} unlock={unlock} onStart={startSession} onOpenPreset={setPreset} haptics={haptics} goLibrary={() => setTab('flow')} activeProfile={activeProfile} />}
        {tab === 'flow' && <BFFlow theme={theme} accentH={accentH} unlock={unlock} onOpenPreset={setPreset} haptics={haptics} activeProfile={activeProfile} />}
        {tab === 'progress' && <BFProgress theme={theme} accentH={accentH} session={session} unlock={unlock} />}
        {tab === 'settings' && <BFSettings theme={theme} accentH={accentH} setAccent={setAccentKey} vizStyle={vizStyle} setVizStyle={setVizStyle} voice={voice} setVoice={setVoice} voicePack={voicePack} setVoicePack={setVoicePack} ttsVoiceName={ttsVoiceName} setTtsVoiceName={setTtsVoiceName} uiLocale={uiLocale} setUiLocale={setUiLocale} sound={sound} setSound={setSound} darkMode={darkMode} setDarkMode={setDarkMode} signedIn={signedIn} setSignedIn={setSignedIn} user={user} resetOnboarded={resetOnboarded} fontPairing={fontPairing} setFontPairing={setFontPairing} ambientId={ambientId} setAmbientId={setAmbientId} ambientVol={ambientVol} setAmbientVol={setAmbientVol} haptics={haptics} setHaptics={setHaptics} onOpenAdmin={() => setTab('admin')} />}
        {tab === 'admin' && <BFAdmin theme={theme} accentH={accentH} onBack={() => setTab('settings')} />}
      </div>
      {!compactTabs && !preset && !active && !complete && (
        <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, zIndex: 40 }}>
          <BFTabBar tab={tab === 'admin' ? 'settings' : tab} setTab={guardedSetTab} theme={theme} accentH={accentH} kidsMode={kidsMode} showFlow={showFlow} />
        </div>
      )}

      {!active && !complete && !preset && <BFHelpButton theme={theme} accentH={accentH} compactTabs={compactTabs} onClick={() => setHelpOpen(true)} />}

      {preset && <BFPresetDetail pattern={preset} theme={theme} accentH={accentH} onClose={() => setPreset(null)} onStart={startSession} haptics={haptics} />}
      {active && <BFPlayer pattern={active.pattern} totalSeconds={active.duration} vizStyle={vizStyle} setVizStyle={setVizStyle} accentH={accentH} theme={theme} voice={voice} voicePack={voicePack} sound={sound} ambientId={ambientId} ambientVol={ambientVol} haptics={haptics} onClose={() => setActive(null)} onComplete={onComplete} />}
      {complete && <BFComplete pattern={complete.pattern} duration={complete.duration} cycles={complete.cycles} theme={theme} accentH={accentH} voice={voice} voicePack={voicePack} onDone={() => setComplete(null)} />}
      {helpOpen && <BFHelp theme={theme} accentH={accentH} onClose={() => setHelpOpen(false)} />}
    </div>
  );
}

Object.assign(window, { BFApp });
