/* ============================================================
   App root — bottom tab navigation + a detail-view stack.
   Tabs: Dashboard · Translate · Dictionary · Categories.
   Detail views (category, lesson, alphabet, complete, review)
   push over the active tab full-screen and hide the tab bar.
   ============================================================ */
(function () {
  const { useState, useEffect } = React;

  const TABS = [
    { id:'dashboard', label:'Home' },
    { id:'lessons', label:'Lessons' },
    { id:'categories', label:'Categories' },
    { id:'translate', label:'Translate' },
    { id:'dictionary', label:'Dictionary' },
  ];
  const TAB_SCREENS = {
    dashboard: () => window.Home,
    lessons: () => window.Lessons,
    categories: () => window.Categories,
    translate: () => window.Translate,
    dictionary: () => window.Dictionary,
  };
  const DETAIL_SCREENS = {
    category: () => window.Category,
    lesson: () => window.WordGrid,
    alphabet: () => window.AlphabetGrid,
    complete: () => window.Complete,
    review: () => window.Review,
    auth: () => window.AuthScreen,
    profile: () => window.Profile,
    'profile-edit': () => window.ProfileEdit,
    'profile-email': () => window.ProfileEmail,
    'profile-password': () => window.ProfilePassword,
    'profile-appearance': () => window.ProfileAppearance,
    'profile-goal': () => window.ProfileGoal,
  };

  // ============================================================
  // 2026-06-05 7:25PM — DB hydration + streak helpers (doc 04 §5).
  // Rebuilds window.APP.learner from profiles + user_settings + user_streaks
  // whenever auth state changes, so existing screens keep reading L.name etc.
  // Status: ⚠️ PENDING USER TESTING
  // ============================================================
  const DBG = () => { try { return localStorage.getItem('auth:debug') === '1'; } catch (e) { return false; } };
  const log = (...a) => { if (DBG()) console.log('[auth:app]', ...a); };

  function localDateStr(d) {
    d = d || new Date();
    const y = d.getFullYear(), m = String(d.getMonth() + 1).padStart(2, '0'), day = String(d.getDate()).padStart(2, '0');
    return `${y}-${m}-${day}`; // local calendar date, not UTC (timezone correctness per §5.2)
  }

  // Compute forgiving streak from descending-ordered activity_date rows with minutes>0 (doc 04 §5.1).
  function computeStreak(rows) {
    const days = (rows || []).filter(r => (r.minutes || 0) > 0).map(r => r.activity_date).sort().reverse();
    if (!days.length) return 0;
    const today = localDateStr();
    const yest = localDateStr(new Date(Date.now() - 86400000));
    if (days[0] !== today && days[0] !== yest) return 0; // most recent practice is older than yesterday
    let streak = 1;
    let prev = new Date(days[0] + 'T00:00:00');
    for (let i = 1; i < days.length; i++) {
      const cur = new Date(days[i] + 'T00:00:00');
      const gap = Math.round((prev - cur) / 86400000);
      if (gap === 1) { streak++; prev = cur; }
      else if (gap === 0) { continue; } // same day, ignore
      else break;
    }
    return streak;
  }

  // One-time merge of anonymous localStorage progress into the account (idempotent).
  async function mergeLocalProgress(userId) {
    try {
      if (localStorage.getItem('ru_merged_v1') === '1') return;
      if (!window.sb) return;
      const rows = [];
      try {
        const alpha = JSON.parse(localStorage.getItem('ru_alpha_learned_v1') || '{}');
        Object.keys(alpha || {}).forEach(letter => {
          if (alpha[letter]) rows.push({ user_id: userId, item_type: 'letter', item_key: letter, status: 'learned', completed_at: new Date().toISOString() });
        });
      } catch (e) {}
      // any ru_lesson_* keys written by an anonymous Complete()
      try {
        for (let i = 0; i < localStorage.length; i++) {
          const k = localStorage.key(i);
          if (k && k.indexOf('ru_lesson_') === 0) {
            const lessonId = k.slice('ru_lesson_'.length);
            let acc = null;
            try { const v = JSON.parse(localStorage.getItem(k)); acc = v && typeof v.accuracy === 'number' ? v.accuracy : null; } catch (e) {}
            rows.push({ user_id: userId, item_type: 'lesson', item_key: lessonId, status: 'done', accuracy: acc, completed_at: new Date().toISOString() });
          }
        }
      } catch (e) {}
      if (rows.length) {
        const { error } = await window.sb.from('user_progress').upsert(rows, { onConflict: 'user_id,item_type,item_key' });
        if (error) { console.error('[auth:app] merge upsert failed', error.message); return; } // don't mark merged on failure
      }
      localStorage.setItem('ru_merged_v1', '1');
      log('merged local progress rows:', rows.length);
    } catch (e) {
      console.error('[auth:app] mergeLocalProgress error', e);
    }
  }

  // Hydrate window.APP.learner from DB for the signed-in user; falls back to guest on error.
  async function hydrateLearner(session) {
    if (!session || !session.user) {
      window.APP.learner = window.APP.guestLearner();
      log('hydrate: guest');
      return;
    }
    const uid = session.user.id;
    const email = session.user.email || '';
    try {
      await mergeLocalProgress(uid);
      const [prof, settings, streaks] = await Promise.all([
        window.sb.from('profiles').select('display_name,avatar_url,cefr_level,created_at').eq('id', uid).maybeSingle(),
        window.sb.from('user_settings').select('daily_goal_minutes,theme').eq('user_id', uid).maybeSingle(),
        window.sb.from('user_streaks').select('activity_date,minutes').eq('user_id', uid).order('activity_date', { ascending: false }).limit(400),
      ]);
      if (prof.error) console.error('[auth:app] profiles read', prof.error.message);
      if (settings.error) console.error('[auth:app] user_settings read', settings.error.message);
      if (streaks.error) console.error('[auth:app] user_streaks read', streaks.error.message);

      const p = prof.data || {};
      const s = settings.data || {};
      const rows = streaks.data || [];
      const today = localDateStr();
      const todayRow = rows.find(r => r.activity_date === today);
      const fullName = p.display_name || (email ? email.split('@')[0] : 'Learner');
      window.APP.learner = {
        name: (fullName.split(' ')[0]) || 'Learner',
        fullName,
        email,
        level: p.cefr_level || 'A1',
        streak: computeStreak(rows),
        dayGoalMin: s.daily_goal_minutes || 10,
        doneToday: todayRow ? (todayRow.minutes || 0) : 0,
        memberSince: p.created_at ? new Date(p.created_at).getFullYear() : new Date().getFullYear(),
        avatarUrl: p.avatar_url || null,
        isGuest: false,
      };
      log('hydrate: signed-in', window.APP.learner);
    } catch (e) {
      console.error('[auth:app] hydrateLearner error', e);
      window.APP.learner = window.APP.guestLearner();
    }
  }

  function TabIcon({ id, active }) {
    const c = active ? 'var(--primary)' : 'var(--muted)';
    const sw = 2;
    if (id === 'dashboard') return (
      <svg width="23" height="23" viewBox="0 0 24 24" fill="none"><path d="M4 11l8-7 8 7M6 10v9h12v-9" stroke={c} strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" /></svg>);
    if (id === 'lessons') return (
      <svg width="23" height="23" viewBox="0 0 24 24" fill="none"><path d="M6 3v18M6 4h11l-2.5 3.5L17 11H6" stroke={c} strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" /></svg>);
    if (id === 'translate') return (
      <svg width="23" height="23" viewBox="0 0 24 24" fill="none"><path d="M4 6h9M8 4v2c0 4-2 7-5 8.5" stroke={c} strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" /><path d="M6.5 11c1 2 3 3.5 5 4.2" stroke={c} strokeWidth={sw} strokeLinecap="round" /><path d="M12.5 20l3.5-8 3.5 8M14 17h4" stroke={c} strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" /></svg>);
    if (id === 'dictionary') return (
      <svg width="23" height="23" viewBox="0 0 24 24" fill="none"><path d="M5 4h11a2 2 0 012 2v14H7a2 2 0 01-2-2V4z" stroke={c} strokeWidth={sw} strokeLinejoin="round" /><path d="M5 18a2 2 0 012-2h11" stroke={c} strokeWidth={sw} strokeLinejoin="round" /><path d="M9 8h6M9 11h4" stroke={c} strokeWidth={sw} strokeLinecap="round" /></svg>);
    if (id === 'profile') return (
      <svg width="23" height="23" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="8" r="3.6" stroke={c} strokeWidth={sw} /><path d="M5 20c0-3.6 3.1-5.6 7-5.6s7 2 7 5.6" stroke={c} strokeWidth={sw} strokeLinecap="round" /></svg>);
    return (
      <svg width="23" height="23" viewBox="0 0 24 24" fill="none"><rect x="4" y="4" width="7" height="7" rx="2" stroke={c} strokeWidth={sw} /><rect x="13" y="4" width="7" height="7" rx="2" stroke={c} strokeWidth={sw} /><rect x="4" y="13" width="7" height="7" rx="2" stroke={c} strokeWidth={sw} /><rect x="13" y="13" width="7" height="7" rx="2" stroke={c} strokeWidth={sw} /></svg>);
  }

  function TabBar({ tab, onTab }) {
    return (
      <div style={{ position:'absolute', left:0, right:0, bottom:0, zIndex:40,
        background:'rgba(255,255,255,0.86)', backdropFilter:'blur(16px)', WebkitBackdropFilter:'blur(16px)',
        borderTop:'1px solid var(--line)', display:'flex',
        padding:'8px 2px calc(8px + env(safe-area-inset-bottom))' }}>
        {TABS.map(t => (
          <button key={t.id} onClick={() => onTab(t.id)}
            style={{ appearance:'none', border:'none', background:'transparent', cursor:'pointer', flex:1,
              display:'flex', flexDirection:'column', alignItems:'center', gap:3, padding:'4px 1px' }}>
            <TabIcon id={t.id} active={tab === t.id} />
            <span style={{ fontFamily:'"Hanken Grotesk",system-ui', fontWeight:700, fontSize:9.5, whiteSpace:'nowrap',
              color: tab === t.id ? 'var(--primary)' : 'var(--muted)' }}>{t.label}</span>
          </button>
        ))}
      </div>
    );
  }

  function App() {
    const [tab, setTab] = useState('dashboard');
    const [stack, setStack] = useState([]); // detail views over the tab
    const [dir, setDir] = useState('fwd');
    const [session, setSession] = useState(null);
    const [hydrated, setHydrated] = useState(false); // bumps to force re-render after DB hydrate

    // 2026-06-05 7:25PM — session bootstrap + auth-change subscription. PENDING USER TESTING
    useEffect(() => {
      if (!window.sb) { setHydrated(true); return; } // CDN failed; run as guest
      let live = true;
      window.sb.auth.getSession().then(async ({ data }) => {
        const sess = (data && data.session) || null;
        await hydrateLearner(sess);
        if (!live) return;
        setSession(sess);
        setHydrated(true);
      });
      const { data: sub } = window.sb.auth.onAuthStateChange(async (_event, sess) => {
        log('onAuthStateChange', _event, !!sess);
        await hydrateLearner(sess || null);
        if (!live) return;
        setSession(sess || null);
        setHydrated(h => !h); // bump to re-render screens reading window.APP.learner
        // on sign-in, drop back to home so the freshly-hydrated dashboard shows
        if (_event === 'SIGNED_IN') { setDir('back'); setStack([]); setTab('dashboard'); }
      });
      return () => { live = false; if (sub && sub.subscription) sub.subscription.unsubscribe(); };
    }, []);

    const nav = {
      go:  (name, params = {}) => {
        // Auth wall (doc 04 §2.3): Profile is meaningless without an account → route to 'auth'.
        if (name === 'profile' && !session) { setDir('fwd'); setStack(s => [...s, { name: 'auth', params: {} }]); return; }
        setDir('fwd'); setStack(s => [...s, { name, params }]);
      },
      back:() => { setDir('back'); setStack(s => s.slice(0, -1)); },
      home:() => { setDir('back'); setStack([]); },
      setTab:(t) => { setDir('fwd'); setStack([]); setTab(t); },
      session,                 // expose to screens that need to gate on auth
      refresh:() => setHydrated(h => !h),
      rehydrate: async () => { const { data } = await window.sb.auth.getSession(); await hydrateLearner((data && data.session) || null); setHydrated(h => !h); },
    };

    // 2026-06-05 6:55PM — device frame removed: prototype now runs as a normal web app.
    // What: dropped <IOSDevice> bezel + scale-to-fit; app fills the real viewport
    //       (full-bleed on phones, centered 480px column on desktop).
    // Why: Christopher wants to test it as a browser app on russianlearning.app,
    //      not as a device mock. Deployment-copy change only; verbatim prototype
    //      (with frame) stays in vanpoos/russian-learning-prototype.
    // Status: ⚠️ PENDING USER TESTING
    const detail = stack[stack.length - 1];
    let Screen, key, showBar;
    if (detail) {
      Screen = DETAIL_SCREENS[detail.name]();
      key = 'd' + stack.length + detail.name;
      showBar = false;
    } else {
      Screen = TAB_SCREENS[tab]();
      key = 't' + tab;
      showBar = true;
    }
    const params = detail ? detail.params : {};
    // suffix key with hydration flag so a DB-hydrate bump re-mounts the current screen,
    // forcing a fresh read of the (now updated) window.APP.learner global.
    key = key + (hydrated ? 'H' : 'h');

    return (
      <div style={{ minHeight:'100dvh', width:'100%', display:'flex', justifyContent:'center',
        background:'var(--stage)' }}>
        <div style={{ position:'relative', width:'100%', maxWidth:480, height:'100dvh', overflow:'hidden',
          background:'#F2F2F7', boxShadow:'0 0 24px rgba(0,0,0,0.08), 0 0 0 1px rgba(0,0,0,0.06)' }}>
          <div key={key} style={{ height:'100%', overflowY:'auto',
            animation:`${dir === 'fwd' ? 'screenIn' : 'screenBack'} .3s cubic-bezier(.3,.9,.3,1)` }}>
            <Screen nav={nav} {...params} />
          </div>
          {showBar && <TabBar tab={tab} onTab={nav.setTab} />}
        </div>
      </div>
    );
  }

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