// 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 = 'rain', 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 [myPatternsVer, setMyPatternsVer] = React.useState(0);
  // Family session picker — null when closed, truthy (an opts object) when
  // the participant+pattern+duration modal is open. On Start we write
  // the chosen participant ids onto `active.familyParticipants` so Phase
  // 3b's session writer can fan out N rows with a shared family_session_id.
  const [familyPick, setFamilyPick] = React.useState(null);
  // Family progress dashboard — null when closed, truthy when open. Read-only
  // per-profile stats view; pulls data directly via BFSessions.statsByProfile.
  const [familyDash, setFamilyDash] = React.useState(null);
  // BOLT history is persisted locally via BFBoltHistory (previously ephemeral
  // React state that reset on reload). Seeded empty and hydrated async on
  // mount. When signed in + online, BFRemoteSync.pushBoltRow mirrors each new
  // score to Supabase, and the 'bf:bolt:synced' event fires after a pull so
  // we re-hydrate with any rows captured on another device.
  const [boltHistory, setBoltHistory] = React.useState([]);
  const hydrateBolt = React.useCallback(async () => {
    if (typeof BFBoltHistory === 'undefined') return;
    const rows = await BFBoltHistory.all();
    setBoltHistory(rows);
  }, []);
  React.useEffect(() => {
    let cancelled = false;
    hydrateBolt().then(() => { /* seeded */ });
    const onSynced = () => { if (!cancelled) hydrateBolt(); };
    window.addEventListener('bf:bolt:synced', onSynced);
    return () => {
      cancelled = true;
      window.removeEventListener('bf:bolt:synced', onSynced);
    };
  }, [hydrateBolt]);
  const saveBolt = React.useCallback(async (score) => {
    try {
      const row = await BFBoltHistory.append({ score });
      setBoltHistory((h) => [...h, row]);
      // Best-effort remote push. Fails silently if signed out or offline.
      if (window.BFRemoteSync && typeof BFRemoteSync.pushBoltRow === 'function') {
        BFRemoteSync.pushBoltRow(row).catch(() => {});
      }
    } catch (e) { /* storage error — keep prior list */ }
  }, []);
  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 ─────────────────────────
  // Pulled from BFProfiles.current() on mount and kept in sync via the
  // subscribe callback. Drives audience filtering on Home / Library and
  // tab-bar / route gating (kids can't reach BOLT or Admin).
  const [activeProfile, setActiveProfile] = React.useState(() => {
    try { return (window.BFProfiles && window.BFProfiles.current()) || null; }
    catch (e) { return null; }
  });
  React.useEffect(() => {
    if (!window.BFProfiles || !window.BFProfiles.subscribe) return;
    const off = window.BFProfiles.subscribe((snap) => {
      setActiveProfile(snap.current || null);
    });
    // In case BFProfiles resolved before we subscribed.
    try { setActiveProfile(window.BFProfiles.current() || null); } catch (e) {}
    return off;
  }, []);
  const kidsMode = !!(activeProfile && activeProfile.kind === 'kid');

  // Kids can't land on the BOLT tab (breath-hold-to-discomfort isn't
  // appropriate) or the Admin tab (privileged). Auto-bounce to Home if the
  // active profile flips to a kid while we're on a gated tab.
  React.useEffect(() => {
    if (kidsMode && (tab === 'bolt' || tab === 'admin')) {
      setTab('home');
    }
  }, [kidsMode, tab]);

  // PIN gate on Settings when a kid is active. Tapping the Settings tab
  // runs the parent PIN prompt first; cancelling leaves the tab alone.
  // Other tabs (Home / Flow / Progress) are free. BOLT and Admin are
  // hidden / auto-bounced above, so we don't reach them here.
  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]);

  const startSession = (pattern, duration, opts) => {
    // Hard safety: block adult-audience patterns from launching under a
    // kid profile even if the id arrived from a stale URL, deep-link, or
    // leftover React state. UI layer already filters the list, but this
    // is the last line of defence.
    if (typeof bfPatternAllowedForProfile === 'function'
        && !bfPatternAllowedForProfile(pattern, activeProfile)) {
      try { BFToast.error('Not available', 'This pattern isn\u2019t available for the current profile.'); } catch (e) {}
      return;
    }
    // Family sessions carry a list of participant profile ids. Re-validate
    // the pattern against every participant's audience so an adult-only
    // pattern can never leak into a group that includes a kid, even if
    // the picker's UI-level filter was bypassed.
    const familyParticipants = (opts && Array.isArray(opts.familyParticipants) && opts.familyParticipants.length)
      ? opts.familyParticipants.slice()
      : null;
    if (familyParticipants && window.BFProfiles && typeof window.BFProfiles.all === 'function') {
      const roster = window.BFProfiles.all();
      const allAllowed = familyParticipants.every((id) => {
        const p = roster.find((r) => r.id === id);
        return p && (typeof bfPatternAllowedForProfile !== 'function' || bfPatternAllowedForProfile(pattern, p));
      });
      if (!allAllowed) {
        try { BFToast.error('Not available', 'One or more participants can\u2019t use this pattern.'); } catch (e) {}
        return;
      }
    }
    setPreset(null);
    setFamilyPick(null);
    setActive({ pattern, duration, familyParticipants });
  };

  const onComplete = async () => {
    const done = active;
    const cycles = Math.floor(done.duration / bfCycleSeconds(done.pattern));
    const participants = Array.isArray(done.familyParticipants) ? done.familyParticipants : null;
    setComplete({ pattern: done.pattern, duration: done.duration, cycles, familyParticipants: participants });
    setActive(null);
    // Persist + evaluate milestones and streak tiers. Fire congratulations
    // toasts staggered so they don't pile up. Milestones first (smaller set),
    // then tiers. Refresh the stats used by Home + Progress.
    //
    // Family-mode fan-out: when `familyParticipants` is set, write one row
    // per participant sharing a single `familySessionId`. Milestones still
    // evaluate against the signed-in user's aggregate — it's their device,
    // their journey; per-participant history surfaces on the Family dashboard.
    try {
      const res = participants
        ? await BFSessions.completeFamily({
            patternId:   done.pattern.id,
            patternName: done.pattern.name,
            durationSec: done.duration,
            cycles,
          }, participants)
        : await BFSessions.complete({
            patternId:   done.pattern.id,
            patternName: done.pattern.name,
            durationSec: done.duration,
            cycles,
          });
      setSession(res.stats);
      const queue = [];
      const i18n = window.BFI18n;
      const tMilestoneTitle = (i18n && i18n.t('app.milestone_unlocked_title')) || 'Milestone unlocked';
      (res.newlyUnlocked || []).forEach((m) => {
        queue.push({ title: tMilestoneTitle, body: bfMilestoneName(m) });
      });
      (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} /></div>}
      <div style={{ flex: 1, position: 'relative', overflow: 'auto' }}>
        {tab === 'home' && <BFHome theme={theme} accentH={accentH} session={session} onStart={startSession} onOpenPreset={setPreset} onOpenFamily={() => setFamilyPick({})} onOpenFamilyDash={() => setFamilyDash({})} haptics={haptics} goLibrary={() => setTab('flow')} activeProfile={activeProfile} />}
        {tab === 'flow' && <BFFlow theme={theme} accentH={accentH} onOpenPreset={setPreset} haptics={haptics} myPatternsVer={myPatternsVer} activeProfile={activeProfile} />}
        {tab === 'bolt' && <BFBolt theme={theme} accentH={accentH} voice={voice} voicePack={voicePack} history={boltHistory} onSave={saveBolt} />}
        {tab === 'progress' && <BFProgress theme={theme} accentH={accentH} session={session} />}
        {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} onOpenCustom={() => setPreset(BF_CUSTOM_PATTERN)} onOpenAdmin={() => setTab('admin')} />}
        {tab === 'admin' && <BFAdmin theme={theme} accentH={accentH} onBack={() => setTab('settings')} />}
      </div>
      {!compactTabs && !preset && !active && !complete && !familyPick && !familyDash && (
        <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} />
        </div>
      )}

      {!active && !complete && !preset && !familyPick && !familyDash && <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} onSavedAsNew={() => { setMyPatternsVer(v => v + 1); setTab('flow'); }} />}
      {familyPick && <BFFamilyPicker theme={theme} accentH={accentH} haptics={haptics} onClose={() => setFamilyPick(null)} onStart={(pattern, duration, ids) => { startSession(pattern, duration, { familyParticipants: ids }); }} />}
      {familyDash && <BFFamilyDashboard theme={theme} accentH={accentH} haptics={haptics} onClose={() => setFamilyDash(null)} />}
      {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 });
