// app.jsx — root for Nurse CNE
// Modes: loading → reset → onboarding → (guest OR ready)
// Guest mode: all browse routes (home, discover, course detail, profile-prefs)
// work without a session. Auth-required actions (enroll, save, log, view record,
// view saved, view reminders) prompt an overlay sign-in via requireAuth().

const { useState: aS, useEffect: aE, useMemo: aM, useCallback: aC, useRef: aR } = React;

function NurseCNEApp() {
  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "lang": "en",
    "theme": "light",
    "density": "regular",
    "discoverView": "list",
    "showWalletDetails": true
  }/*EDITMODE-END*/;

  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const lang = t.lang;
  const setLang = (v) => setTweak('lang', v);

  // ── Session bootstrap ─────────────────────────────────────────────────
  // authState: 'loading' (initial) | 'reset' (deep-link) | 'onboarding' | 'ready'
  // Note: there is no longer a forced 'auth' state — guests use the app with
  // a null session, and authOverlay holds the prompt state for gated actions.
  const [authState, setAuthState] = aS('loading');
  const [session, setSession]     = aS(null);
  const [profile, setProfile]     = aS(null);

  // ── Course catalogue (fetched from Supabase cne_courses) ──────────────
  // Starts with the locally-bundled fallback (data.js → COURSES) so the
  // UI never renders empty. After mount we replace it with the live
  // Supabase rows. `coursesVersion` is bumped to force a re-render.
  const [coursesVersion, setCoursesVersion] = aS(0);
  const [coursesLoading, setCoursesLoading] = aS(true);
  const [coursesError,   setCoursesError]   = aS(null);

  // ── Providers directory (cne_providers + course counts) ──────────────
  const [providers,        setProviders]        = aS([]);
  const [providersLoading, setProvidersLoading] = aS(true);
  const [openProviderCode, setOpenProviderCode] = aS(null);
  const [discoverInitFilter, setDiscoverInitFilter] = aS(null);

  aE(() => {
    let mounted = true;
    (async () => {
      try {
        const { data, error } = await DB.fetchCourses({ status: 'active' });
        if (!mounted) return;
        if (error) { setCoursesError(error.message || String(error)); return; }
        if (data && data.length > 0) {
          window.setCourses(data);          // replaces window.COURSES in-place
          setCoursesVersion(v => v + 1);
        }
      } catch (e) {
        if (mounted) setCoursesError(e.message || String(e));
      } finally {
        if (mounted) setCoursesLoading(false);
      }
    })();
    (async () => {
      try {
        const { data, error } = await DB.fetchProviders();
        if (!mounted) return;
        if (!error && data) setProviders(data);
      } catch (e) {
        console.warn('fetchProviders failed', e);
      } finally {
        if (mounted) setProvidersLoading(false);
      }
    })();
    return () => { mounted = false; };
  }, []);

  aE(() => {
    let mounted = true;
    let settled = false;

    // Safety net — if Supabase session restoration stalls (cold start, slow
    // network, or a thrown error in detectSessionInUrl), force the app out
    // of the loading state after 4s so the user never has to manually
    // refresh. Guests just see the normal home screen; if a real session
    // exists, onAuthChange will populate it shortly after.
    const fallbackTimer = setTimeout(() => {
      if (!mounted || settled) return;
      console.warn('Auth bootstrap timed out — entering guest mode.');
      settled = true;
      setAuthState('ready');
    }, 4000);

    const finalize = async (s, source) => {
      if (!mounted) return;
      try {
        if (window.location.hash.includes('type=recovery')) {
          settled = true; setAuthState('reset'); return;
        }
        if (!s) {
          setSession(null); setProfile(null);
          settled = true; setAuthState('ready'); return;
        }
        setSession(s);
        const p = await DB.getProfile(s.user.id).catch(err => {
          console.warn('getProfile failed during bootstrap', err);
          return null;
        });
        if (!mounted) return;
        setProfile(p);
        settled = true;
        setAuthState(p?.onboarded ? 'ready' : (source === 'change' ? 'onboarding' : 'onboarding'));
      } catch (e) {
        console.warn('Auth finalize failed', e);
        if (mounted) { settled = true; setAuthState('ready'); }
      }
    };

    AuthAPI.getSession()
      .then((s) => finalize(s, 'initial'))
      .catch((e) => {
        console.warn('getSession failed', e);
        if (mounted && !settled) { settled = true; setAuthState('ready'); }
      });

    const unsub = AuthAPI.onAuthChange((s) => {
      // After bootstrap we keep using onAuthChange to react to sign-in /
      // sign-out events. The same finalize() handles both paths so the
      // two code paths cannot diverge.
      finalize(s, 'change');
    });

    return () => { mounted = false; clearTimeout(fallbackTimer); unsub(); };
  }, []);

  // ── Pull prefs from profile once, then sync changes back ─────────────
  aE(() => {
    if (profile?.prefs) {
      if (profile.prefs.lang    && profile.prefs.lang    !== t.lang)    setTweak('lang',    profile.prefs.lang);
      if (profile.prefs.theme   && profile.prefs.theme   !== t.theme)   setTweak('theme',   profile.prefs.theme);
      if (profile.prefs.density && profile.prefs.density !== t.density) setTweak('density', profile.prefs.density);
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [profile?.id]);

  aE(() => {
    if (!session || authState !== 'ready') return;
    DB.setPrefs(session.user.id, { lang: t.lang, theme: t.theme, density: t.density });
  }, [t.lang, t.theme, t.density, session?.user?.id, authState]);

  // ── Apply theme + density to root ────────────────────────────────────
  aE(() => {
    const sysDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    const eff = t.theme === 'auto' ? (sysDark ? 'dark' : '') : (t.theme === 'dark' ? 'dark' : '');
    document.documentElement.dataset.theme = eff;
    document.documentElement.dataset.density = t.density;
  }, [t.theme, t.density]);

  // ── User data (only loaded when signed in) ────────────────────────────
  const [savedIds, setSavedIds]       = aS(new Set());
  const [enrolledIds, setEnrolledIds] = aS(new Set());
  const [records, setRecords]         = aS([]);
  const [reminders, setReminders]     = aS([]);

  // Client-side derived reminders (only meaningful for signed-in users)
  const allReminders = aM(() => {
    if (!session) return [];
    const derived = (typeof deriveReminders === 'function')
      ? deriveReminders({
          profile,
          enrolledIds: [...enrolledIds],
          records,
          courses: (typeof COURSES !== 'undefined' ? COURSES : []),
        })
      : [];
    const stored = reminders || [];
    const seen = new Set(stored.map(r => r.id));
    return [...stored, ...derived.filter(r => !seen.has(r.id))];
  }, [session, profile, enrolledIds, records, reminders]);

  const reloadUserData = aC(async () => {
    if (!session) return;
    const [s, e, r, rm] = await Promise.all([
      DB.getSaved(session.user.id),
      DB.getEnrolled(session.user.id),
      DB.getRecords(session.user.id),
      DB.getReminders(session.user.id),
    ]);
    setSavedIds(new Set(s));
    setEnrolledIds(new Set(e));
    setRecords(r);
    setReminders(rm);
  }, [session]);

  aE(() => {
    if (authState === 'ready' && session) reloadUserData();
  }, [authState, session, reloadUserData]);

  // ── Auth overlay + requireAuth helper ─────────────────────────────────
  // authOverlay: null | { intent: 'save'|'enroll'|'log'|'record'|'saved'|'reminders'|'default' }
  const [authOverlay, setAuthOverlay] = aS(null);
  const pendingActionRef = aR(null);

  // When the user successfully signs in, run any queued action and close.
  aE(() => {
    if (session && pendingActionRef.current) {
      const fn = pendingActionRef.current;
      pendingActionRef.current = null;
      setAuthOverlay(null);
      // Defer so reloadUserData() has a chance to run first.
      setTimeout(() => fn(session), 0);
    }
    if (session && authOverlay) {
      // Signed in with no pending action — just close.
      setAuthOverlay(null);
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [session]);

  const requireAuth = aC((action, intent) => {
    if (session) { action?.(session); return; }
    pendingActionRef.current = action || null;
    setAuthOverlay({ intent: intent || 'default' });
  }, [session]);

  const closeAuthOverlay = aC(() => {
    pendingActionRef.current = null;
    setAuthOverlay(null);
  }, []);

  // ── Gated actions (auto-prompt sign-in when needed) ───────────────────
  const toggleSaved = aC((id) => {
    requireAuth(async (sess) => {
      const have = savedIds.has(id);
      setSavedIds(prev => { const n = new Set(prev); have ? n.delete(id) : n.add(id); return n; });
      await DB.toggleSaved(sess.user.id, id, have);
    }, 'save');
  }, [savedIds, requireAuth]);

  const toggleEnrolled = aC((id) => {
    requireAuth(async (sess) => {
      const have = enrolledIds.has(id);
      setEnrolledIds(prev => { const n = new Set(prev); have ? n.delete(id) : n.add(id); return n; });
      await DB.toggleEnrolled(sess.user.id, id, have);
    }, 'enroll');
  }, [enrolledIds, requireAuth]);

  const addRecord = aC((rec) => new Promise((resolve) => {
    requireAuth(async (sess) => {
      const { data, error } = await DB.addRecord(sess.user.id, { ...rec, courseId: rec.courseId || null });
      if (error) { console.warn('addRecord failed', error); resolve({ error }); return; }
      if (rec.certificateFile && data?.id) {
        try {
          const up = await DB.uploadCertificate(sess.user.id, data.id, rec.certificateFile);
          if (up?.error) console.warn('uploadCertificate failed', up.error);
        } catch (e) { console.warn('uploadCertificate threw', e); }
      }
      await reloadUserData();
      resolve({ data });
    }, 'log');
  }), [reloadUserData, requireAuth]);

  const signOut = aC(async () => {
    await AuthAPI.signOut();
    setSavedIds(new Set()); setEnrolledIds(new Set()); setRecords([]); setReminders([]);
    setProfile(null); setSession(null);
    setTab('home');
    setAuthState('ready');     // stay in guest mode after sign-out
  }, []);

  // ── Navigation state ──────────────────────────────────────────────────
  const [tab, setTab] = aS('home');
  const [openCourseId, setOpenCourseId] = aS(null);
  const [sheet, setSheet] = aS(null);
  // Which sub-section of Profile to open (overview / record / saved).
  const [profileSection, setProfileSection] = aS('overview');

  // Tab change.
  //   • Profile (我的) — no longer forces a sign-in overlay; guests see the
  //     profile shell with an in-page sign-in card (handled by ProfileScreen's
  //     guest variant). They can still browse Appearance / Preferences.
  //   • Record  (我的記錄) — its own top-level tab for everyone. Guests see
  //     the intro + sample preview; signed-in users see their timeline +
  //     enrolled courses. No longer redirects into Profile.
  const handleTabChange = aC((newTab, section) => {
    if (section) setProfileSection(section);
    // Closing any open course detail when the user switches tabs — otherwise
    // the detail layer keeps overlaying the new tab content and it looks
    // like the nav did nothing.
    setOpenCourseId(null);
    setOpenProviderCode(null);
    setTab(newTab);
  }, []);

  // Upload a certificate file → Supabase storage, then refresh records.
  const uploadCertForRecord = aC(async (recordId, file) => {
    if (!session) return { error: { message: 'Not signed in' } };
    try {
      const res = await DB.uploadCertificate(session.user.id, recordId, file);
      if (res?.error) return { error: res.error };
      await reloadUserData();
      return res;
    } catch (e) {
      return { error: e };
    }
  }, [session, reloadUserData]);

  // Edit / delete a record from the RecordDetailSheet.
  const updateRecord = aC(async (recordId, patch) => {
    if (!session) return { error: { message: 'Not signed in' } };
    const res = await DB.updateRecord(recordId, patch);
    await reloadUserData();
    return res;
  }, [session, reloadUserData]);
  const deleteRecord = aC(async (recordId) => {
    if (!session) return { error: { message: 'Not signed in' } };
    const res = await DB.deleteRecord(recordId);
    await reloadUserData();
    return res;
  }, [session, reloadUserData]);

  // Sheet open — saved, reminders, log all require auth.
  const handleOpenSheet = aC((s) => {
    if (!session && (s === 'saved' || s === 'reminders' || s === 'log')) {
      requireAuth(() => setSheet(s), s);
      return;
    }
    setSheet(s);
  }, [session, requireAuth]);

  const openSignIn = aC(() => {
    setAuthOverlay({ intent: 'signin' });
  }, []);

  // ── Routing ───────────────────────────────────────────────────────────
  if (authState === 'loading') {
    return <VitalSplash label={lang === 'zh' ? '載入中' : 'Loading'} />;
  }
  if (authState === 'reset') {
    return <ResetPasswordScreen lang={lang} setLang={setLang} onDone={() => {
      window.location.hash = '';
      setAuthState('ready');
    }} />;
  }
  if (authState === 'onboarding' && session) {
    return <OnboardingFlow session={session} lang={lang} setLang={setLang}
      onDone={async () => {
        const p = await DB.getProfile(session.user.id);
        setProfile(p);
        setAuthState('ready');
      }} />;
  }

  // ── Main app shell (used for both guest and signed-in) ────────────────
  const sideNavProps = {
    active: tab,
    lang, setLang,
    onLog: () => handleOpenSheet('log'),
    onOpenSheet: handleOpenSheet,
    profile, session,
    onSignIn: openSignIn,
    onSignOut: signOut,
    hasReminders: allReminders.length || 0,
  };

  // The tab screens stay mounted whether or not a course detail is open;
  // the detail is laid over the top of them via CSS. Keeping the underlying
  // tab in the DOM is what preserves the user's scroll position when they
  // hit "back" from a course detail — no manual scroll save/restore needed.
  const detailOpen = !!openCourseId;

  return (
    <div className="app">
      <SideNav {...sideNavProps} onChange={handleTabChange} />
      <div className="app-main">
        <div className="page-stack">
          {/* Underlying tab page — always mounted, kept inert when detail is on top */}
          <div className={'page-layer page-layer--tab' + (detailOpen ? ' is-hidden' : '')}
               aria-hidden={detailOpen}
               {...(detailOpen ? { inert: '' } : {})}>
            {tab === 'home' && (
              <HomeScreen lang={lang} setLang={setLang}
                onOpenCourse={setOpenCourseId}
                onTab={handleTabChange}
                onOpenSheet={handleOpenSheet}
                onSignIn={openSignIn}
                savedIds={savedIds}
                enrolledIds={enrolledIds}
                session={session}
                profile={profile}
                records={records}
                reminders={allReminders}
              />
            )}
            {tab === 'discover' && (
              <DiscoverScreen lang={lang} setLang={setLang}
                onOpenCourse={setOpenCourseId}
                view={t.discoverView}
                setView={(v) => setTweak('discoverView', v)}
                initialFilters={discoverInitFilter}
                onBrowseProviders={() => setTab('providers')}
              />
            )}
            {tab === 'providers' && !openProviderCode && (
              <ProvidersScreen lang={lang} setLang={setLang}
                providers={providers}
                providersLoading={providersLoading}
                onOpenProvider={setOpenProviderCode}
              />
            )}
            {tab === 'providers' && openProviderCode && (
              <ProviderCoursesScreen
                providerCode={openProviderCode}
                providers={providers}
                lang={lang} setLang={setLang}
                onBack={() => setOpenProviderCode(null)}
                onOpenCourse={(id) => { setOpenProviderCode(null); setOpenCourseId(id); }}
              />
            )}
            {tab === 'record' && (
              <RecordScreen
                lang={lang} setLang={setLang}
                session={session}
                profile={profile}
                records={records}
                enrolledIds={enrolledIds}
                onSignIn={openSignIn}
                onLog={() => handleOpenSheet('log')}
                onOpenCourse={setOpenCourseId}
                onUploadCert={uploadCertForRecord}
                onUpdateRecord={updateRecord}
                onDeleteRecord={deleteRecord}
                onCreateRecord={addRecord}
                onToggleEnrolled={toggleEnrolled}
              />
            )}
            {tab === 'profile' && (
              <ProfileScreen lang={lang} setLang={setLang}
                theme={t.theme} setTheme={(v) => setTweak('theme', v)}
                onOpenSheet={handleOpenSheet}
                onSignIn={openSignIn}
                session={session}
                profile={profile}
                records={records}
                enrolledIds={enrolledIds}
                onSignOut={signOut}
                initialSection={profileSection}
                savedIds={savedIds}
                onOpenCourse={setOpenCourseId}
                onUploadCert={uploadCertForRecord}
              />
            )}
          </div>

          {/* Course detail — overlays the tab layer when a course is open */}
          {detailOpen && (
            <div className="page-layer page-layer--detail">
              <CourseDetailScreen
                courseId={openCourseId}
                lang={lang}
                savedIds={savedIds}
                toggleSaved={toggleSaved}
                enrolledIds={enrolledIds}
                toggleEnrolled={toggleEnrolled}
                onBack={() => setOpenCourseId(null)}
                session={session}
              />
            </div>
          )}
        </div>

        <TabBar active={tab} onChange={handleTabChange} lang={lang} onLog={() => handleOpenSheet('log')} />
      </div>

      {sheet === 'log'       && <LogSheet lang={lang} onClose={() => setSheet(null)} onSave={addRecord} />}
      {sheet === 'saved'     && <SavedSheet lang={lang} savedIds={savedIds} onOpenCourse={setOpenCourseId} onClose={() => setSheet(null)} />}
      {sheet === 'reminders' && <RemindersSheet lang={lang} reminders={allReminders} onClose={() => setSheet(null)} onOpenCourse={setOpenCourseId} />}

      {authOverlay && (
        <div className="auth-overlay" onClick={(e) => { if (e.target === e.currentTarget) closeAuthOverlay(); }}>
          <AuthRouter
            lang={lang} setLang={setLang}
            onClose={closeAuthOverlay}
            initialMode={authOverlay.intent === 'signup' ? 'signup' : 'signin'}
          />
        </div>
      )}

      <TweaksPanel>
        <TweakSection label="Language" />
        <TweakRadio label="UI language" value={t.lang}
          options={[{ value: 'en', label: 'EN' }, { value: 'zh', label: '繁中' }]}
          onChange={(v) => setTweak('lang', v)} />
        <TweakSection label="Theme" />
        <TweakRadio label="Mode" value={t.theme}
          options={[{ value: 'light', label: 'Light' }, { value: 'dark', label: 'Dark' }, { value: 'auto', label: 'Auto' }]}
          onChange={(v) => setTweak('theme', v)} />
        <TweakSection label="Layout" />
        <TweakRadio label="Density" value={t.density}
          options={[{ value: 'compact', label: 'Compact' }, { value: 'regular', label: 'Regular' }, { value: 'comfy', label: 'Comfy' }]}
          onChange={(v) => setTweak('density', v)} />
        <TweakRadio label="Discover view" value={t.discoverView}
          options={[{ value: 'list', label: 'List' }, { value: 'grid', label: 'Grid' }]}
          onChange={(v) => setTweak('discoverView', v)} />
      </TweaksPanel>
    </div>
  );
}

window.NurseCNEApp = NurseCNEApp;
