// app.jsx — main app shell with screen routing & state
const { useState: useAppState, useEffect: useAppEffect } = React;
const API = window.BHCAC_API;
const AUTH = window.BHCAC_AUTH;

function App() {
  const [screen, setScreen] = useAppState('today');
  const [adminScreen, setAdminScreen] = useAppState(null);
  const [appData, setAppData] = useAppState(window.BHCAC_DATA);
  const [dataSource, setDataSource] = useAppState('seed');
  const [authUser, setAuthUser] = useAppState(null);
  const [authLoading, setAuthLoading] = useAppState(true);
  const [justCheckedIds, setJustCheckedIds] = useAppState([]);
  const [toast, setToast] = useAppState(null);

  // Load app data + current session in parallel. Also: if the URL has a
  // ?invite=... param, redeem it once the user is signed in.
  useAppEffect(() => {
    let cancelled = false;
    API.loadAll().then(d => {
      if (cancelled) return;
      setAppData(d);
      setDataSource(d._source || 'api');
    });
    AUTH.me().then(async (u) => {
      if (cancelled) return;
      setAuthUser(u);
      setAuthLoading(false);
      try {
        const url = new URL(window.location.href);
        const inviteToken = url.searchParams.get('invite');
        if (inviteToken && u) {
          const updated = await AUTH.acceptInvite(inviteToken);
          setAuthUser(updated);
          showToast('Admin invite accepted · 邀請已接受');
          // Strip the token from the URL so it can't be re-tried / leak via referer
          url.searchParams.delete('invite');
          window.history.replaceState({}, '', url.toString());
        }
      } catch (err) {
        if (err.status !== 401) {
          showToast(`Invite: ${err.message}`, 'error');
        }
      }
    }).catch(() => { if (!cancelled) setAuthLoading(false); });
    return () => { cancelled = true; };
  }, []);

  if (!appData || !appData.members) {
    return <div style={{ padding: 60, textAlign: 'center', color: '#64748b', fontFamily: 'Inter, sans-serif' }}>Loading…</div>;
  }

  // Identity: derived from the authenticated user, falling back to the seeded
  // demo identity (m-001) when the backend is unreachable so the prototype
  // still renders without a server.
  let user = null, family = null;
  if (authUser) {
    const m = authUser.member || {};
    user = {
      ...m,
      id: m.id || authUser.id,
      authUserId: authUser.id,
      email: authUser.email,
      en: authUser.en || m.en || (authUser.email || '').split('@')[0],
      zh: authUser.zh || m.zh || '',
      role: authUser.role,
      hasPassword: authUser.hasPassword,
      passkeyCount: authUser.passkeyCount,
      isAdmin: authUser.role === 'superadmin',
    };
    family = user.family ? (appData.families || []).find(f => f.id === user.family) : null;
  }
  const checkIns = appData.records || [];

  const showToast = (msg, tone = 'success') => {
    setToast({ msg, tone });
    setTimeout(() => setToast(null), 3500);
  };

  const goCheckIn = () => setScreen('checkin');

  const handleConfirm = async (ids) => {
    if (dataSource !== 'api') {
      // Offline / seed-only fallback (rare; only when API unreachable). Just
      // append locally so the prototype screens still work.
      const optimistic = ids.map(id => {
        const m = appData.members.find(x => x.id === id);
        return { date: appData.TODAY, member_id: id, congregation: m?.congregation, source: 'online' };
      });
      setAppData(d => ({ ...d, records: [...d.records, ...optimistic] }));
      setJustCheckedIds(ids);
      setScreen('confirm');
      return;
    }

    // Geofenced flow. Ask the browser for location first (may prompt), then
    // submit to the server which re-verifies. We DON'T optimistically update
    // the UI any more — if the user is too far away, the rejection should be
    // visible, not silently corrected later.
    showToast('Checking your location…', 'info');
    let coords;
    try {
      coords = await API.getCurrentLocation();
    } catch (err) {
      showToast(err.message, 'error');
      return;
    }

    try {
      const r = await API.submitCheckins(ids, { date: appData.TODAY, source: 'online', coords });
      const newRecs = r?.added || ids.map(id => {
        const m = appData.members.find(x => x.id === id);
        return { date: appData.TODAY, member_id: id, congregation: m?.congregation, source: 'online' };
      });
      setAppData(d => ({ ...d, records: [...d.records, ...newRecs] }));
      setJustCheckedIds(ids);
      setScreen('confirm');
    } catch (err) {
      // Server may have included a code/distance — surface a useful message.
      showToast(err.message || 'Check-in failed.', 'error');
    }
  };

  const handleOpenAdmin = () => {
    if (user.role !== 'superadmin') {
      showToast('Admin access is restricted to superadmin accounts.', 'error');
      return;
    }
    setAdminScreen('admin');
  };

  const handleSaveVisitor = async (form) => {
    try {
      const result = await API.addVisitor({ ...form, date: appData.TODAY });
      setAppData(d => ({
        ...d,
        members: [...d.members, result.member],
        records: result.record ? [...d.records, result.record] : d.records,
      }));
      showToast(`Added ${result.member.en} · 新朋友已加入`);
      setAdminScreen('admin');
    } catch (err) {
      showToast(`Add visitor failed: ${err.message}`, 'error');
      throw err;
    }
  };

  const handleAdminCheckIn = async (memberId) => {
    const m = appData.members.find(x => x.id === memberId);
    const rec = { date: appData.TODAY, member_id: memberId, congregation: m?.congregation, source: 'admin' };
    setAppData(d => ({ ...d, records: [...d.records, rec] }));
    try {
      await API.submitCheckins([memberId], { date: appData.TODAY, byAdmin: true });
      showToast(`Checked in ${m?.en}`);
    } catch (err) {
      showToast(`Check-in failed: ${err.message}`, 'error');
    }
  };

  const handleSyncOutline = async () => {
    showToast('Syncing to Outline…', 'info');
    try {
      const r = await API.syncOutline(appData.TODAY);
      showToast(`Synced ${r.records} record(s) to Outline · 已同步`);
    } catch (err) {
      showToast(`Sync failed: ${err.message}`, 'error');
    }
  };

  const handleChangePassword = async ({ currentPassword, newPassword }) => {
    await AUTH.changePassword({ currentPassword, newPassword });
    showToast('Password updated · 密碼已更新');
  };

  const handleExportCSV = () => {
    API.exportMembersCSV(appData.members, appData.congregations);
  };

  const handleExportReport = () => {
    API.exportReportCSV(appData.members, appData.records, appData.congregations);
  };

  const handleUpdateMember = async (id, patch) => {
    try {
      const r = await API.updateMember(id, patch);
      setAppData(d => ({
        ...d,
        members: d.members.map(m => m.id === id ? r.member : m),
      }));
      showToast(`Updated ${r.member.en}`);
      return r.member;
    } catch (err) {
      showToast(`Update failed: ${err.message}`, 'error');
      throw err;
    }
  };

  const handleDeleteMember = async (id) => {
    if (!window.confirm('Delete this member? Their check-in history will be retained but they will no longer appear in the directory.')) return;
    try {
      await API.deleteMember(id);
      setAppData(d => ({ ...d, members: d.members.filter(m => m.id !== id) }));
      showToast('Member deleted · 會友已刪除');
    } catch (err) {
      showToast(`Delete failed: ${err.message}`, 'error');
    }
  };

  const refreshEvents = async () => {
    try {
      const r = await API.listEvents();
      setAppData(d => ({ ...d, events: r.events || [] }));
    } catch (err) {
      console.warn('events refresh failed:', err);
    }
  };

  const handleCreateEvent = async (payload) => {
    try {
      await API.createEvent(payload);
      await refreshEvents();
      showToast('Event created · 活動已建立');
    } catch (err) {
      showToast(`Create failed: ${err.message}`, 'error');
      throw err;
    }
  };

  const handleUpdateEvent = async (id, patch) => {
    try {
      await API.updateEvent(id, patch);
      await refreshEvents();
      showToast('Event updated · 活動已更新');
    } catch (err) {
      showToast(`Update failed: ${err.message}`, 'error');
      throw err;
    }
  };

  const handleDeleteEvent = async (id) => {
    try {
      await API.deleteEvent(id);
      await refreshEvents();
      showToast('Event deleted');
    } catch (err) {
      showToast(`Delete failed: ${err.message}`, 'error');
    }
  };

  const handleImportEvents = async (rows) => {
    try {
      const r = await API.importEvents(rows);
      await refreshEvents();
      showToast(`Imported ${r.created} event${r.created === 1 ? '' : 's'}`);
    } catch (err) {
      showToast(`Import failed: ${err.message}`, 'error');
    }
  };

  const handleSignedIn = async (u) => {
    setAuthUser(u);
    setScreen('today');
    showToast(`Welcome, ${u.en} · 歡迎`);
    // Refresh data — earlier loadAll() likely 401'd before the session existed.
    try {
      const d = await API.loadAll();
      setAppData(d);
      setDataSource(d._source || 'api');
    } catch (err) {
      console.warn('[bhcac] data refresh after sign-in failed:', err);
    }
    // If the URL has ?invite=…, redeem it now that we're signed in.
    try {
      const url = new URL(window.location.href);
      const inviteToken = url.searchParams.get('invite');
      if (inviteToken) {
        const updated = await AUTH.acceptInvite(inviteToken);
        setAuthUser(updated);
        showToast('Admin invite accepted · 邀請已接受');
        url.searchParams.delete('invite');
        window.history.replaceState({}, '', url.toString());
      }
    } catch (err) {
      showToast(`Invite: ${err.message}`, 'error');
    }
  };

  const handleLogout = async () => {
    if (!window.confirm('Sign out?')) return;
    try { await AUTH.logout(); } catch {}
    setAuthUser(null);
    setAdminScreen(null);
    setScreen('today');
    showToast('Signed out');
  };

  const handleAddPasskey = async () => {
    try {
      await AUTH.addPasskey();
      const fresh = await AUTH.me();
      if (fresh) setAuthUser(fresh);
      showToast('Passkey added · 通行密鑰已設定');
    } catch (err) {
      showToast(`Passkey setup failed: ${err.message}`, 'error');
    }
  };

  // Auth gate: any unauthenticated visit lands on the Welcome screen.
  // While the /auth/me probe is still in flight, render the Welcome shell
  // (it'll re-render to Today once the session resolves).
  const requiresAuth = !authUser;

  const renderScreen = () => {
    if (authLoading) {
      return <div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--fg-3)', fontSize: 14 }}>Loading…</div>;
    }
    if (requiresAuth) {
      return <ScreenWelcome onSignIn={handleSignedIn} />;
    }
    if (adminScreen === 'admin') {
      return <ScreenAdmin user={user} members={appData.members} checkIns={checkIns}
        events={appData.events || []}
        onBack={() => setAdminScreen(null)}
        onAddVisitor={() => setAdminScreen('add-visitor')}
        onSyncOutline={handleSyncOutline}
        onExportCSV={handleExportCSV}
        onAdminCheckIn={handleAdminCheckIn}
        onUpdateMember={handleUpdateMember}
        onDeleteMember={handleDeleteMember}
        onCreateEvent={handleCreateEvent}
        onUpdateEvent={handleUpdateEvent}
        onDeleteEvent={handleDeleteEvent}
        onImportEvents={handleImportEvents}
        onExportReport={handleExportReport} />;
    }
    if (adminScreen === 'add-visitor') {
      return <ScreenAddVisitor onBack={() => setAdminScreen('admin')} onSave={handleSaveVisitor} />;
    }
    if (screen === 'welcome') {
      return <ScreenWelcome onSignIn={handleSignedIn} />;
    }
    if (screen === 'today') {
      return <ScreenToday user={user} family={family} members={appData.members}
        events={appData.events || []}
        congregations={appData.congregations || []}
        checkIns={checkIns} onCheckIn={goCheckIn}
        onOpenAdmin={handleOpenAdmin}
        onOpenSettings={() => setScreen('settings')}
        onAddPasskey={authUser ? handleAddPasskey : null} />;
    }
    if (screen === 'settings') {
      return <ScreenSettings user={user}
        onBack={() => setScreen('today')}
        onLogout={handleLogout}
        onAddPasskey={handleAddPasskey}
        onChangePassword={handleChangePassword} />;
    }
    if (screen === 'checkin') {
      return <ScreenCheckIn user={user} family={family} members={appData.members}
        congregations={appData.congregations || []}
        checkIns={checkIns} onConfirm={handleConfirm}
        onBack={() => setScreen('today')} />;
    }
    if (screen === 'confirm') {
      const ids = justCheckedIds.length > 0
        ? justCheckedIds
        : appData.members.filter(m => m.family === family?.id).map(m => m.id);
      return <ScreenConfirm family={family} members={appData.members}
        justCheckedIds={ids} onDone={() => { setScreen('today'); }} />;
    }
  };

  const isWelcomeMode = requiresAuth || screen === 'welcome';
  const currentScreenKey = adminScreen || screen;

  if (isWelcomeMode) {
    return (
      <div className="bhcac-shell" data-mode="welcome">
        {renderScreen()}
        <Toast toast={toast} />
      </div>
    );
  }

  return (
    <div className="bhcac-shell" data-mode="app">
      {authUser && (
        <DesktopHeader user={user} currentScreen={currentScreenKey}
          onHome={() => { setAdminScreen(null); setScreen('today'); }}
          onSettings={() => { setAdminScreen(null); setScreen('settings'); }}
          onAdmin={user.role === 'superadmin' ? handleOpenAdmin : null}
          onLogout={handleLogout} />
      )}
      <div className="bhcac-app-main" data-screen={currentScreenKey}>
        <div className="bhcac-screen">{renderScreen()}</div>
      </div>
      <Toast toast={toast} />
    </div>
  );
}

function DesktopHeader({ user, currentScreen, onHome, onSettings, onAdmin, onLogout }) {
  const showAdminLink = !!onAdmin && user.role === 'superadmin';
  return (
    <header className="bhcac-app-header desktop-only">
      <div className="bhcac-header-inner">
        <button onClick={onHome} style={{
          display: 'flex', alignItems: 'center', gap: 12,
          background: 'transparent', border: 'none', cursor: 'pointer',
          padding: 0, fontFamily: 'inherit',
        }}>
          <img src="assets/bhcac-logo-red.jpg" style={{ width: 40, height: 40, borderRadius: '50%', objectFit: 'cover' }} />
          <div style={{ textAlign: 'left', lineHeight: 1.1 }}>
            <div style={{ fontSize: 18, fontWeight: 700, letterSpacing: '-0.01em', color: 'var(--brand-navy)' }}>BHCAC</div>
            <div style={{ fontSize: 11, fontWeight: 500, letterSpacing: '0.12em', color: 'var(--brand-teal)', marginTop: 2, fontFamily: "'Noto Sans TC', Inter, sans-serif" }}>
              迦南堂 · ATTENDANCE
            </div>
          </div>
        </button>

        <nav style={{ display: 'flex', gap: 28, alignItems: 'center' }}>
          <NavLink active={currentScreen === 'today'} onClick={onHome}>Today · 今日</NavLink>
          {showAdminLink && (
            <NavLink active={currentScreen === 'admin'} onClick={onAdmin}>Admin · 管理員</NavLink>
          )}
          <NavLink active={currentScreen === 'settings'} onClick={onSettings}>Account · 帳戶</NavLink>
        </nav>

        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <button onClick={onSettings} style={{
            display: 'inline-flex', alignItems: 'center', gap: 10,
            padding: '4px 4px 4px 14px',
            borderRadius: 9999,
            background: 'transparent',
            border: '1px solid var(--border-subtle)',
            fontFamily: 'inherit', cursor: 'pointer',
          }}>
            <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--brand-navy)' }}>{user.en?.split(' ')[0] || 'Account'}</span>
            {user.role === 'superadmin' && (
              <span style={{
                fontSize: 9, fontWeight: 700, letterSpacing: '0.08em',
                padding: '2px 7px', borderRadius: 9999,
                background: 'var(--coral-100)', color: 'var(--coral-700)',
              }}>ADMIN</span>
            )}
            <div style={{
              width: 30, height: 30, borderRadius: '50%',
              background: user.avatar || 'var(--brand-coral)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              color: '#fff', fontSize: 12, fontWeight: 700,
            }}>
              {(user.en || '?').slice(0, 1).toUpperCase()}
            </div>
          </button>
          <button onClick={onLogout} title="Sign out" style={{
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            width: 36, height: 36, borderRadius: '50%',
            background: 'transparent', cursor: 'pointer',
            border: '1px solid var(--border-subtle)', color: 'var(--fg-2)',
            fontFamily: 'inherit',
          }}>
            <Icon name="log-out" size={15} />
          </button>
        </div>
      </div>
    </header>
  );
}

function NavLink({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      background: 'transparent', border: 'none', cursor: 'pointer',
      fontSize: 14, fontWeight: 500, fontFamily: 'inherit',
      color: active ? 'var(--brand-coral)' : 'var(--navy-700)',
      padding: '6px 0',
      borderBottom: `2px solid ${active ? 'var(--brand-coral)' : 'transparent'}`,
      transition: 'color 150ms',
    }}>{children}</button>
  );
}

function Toast({ toast }) {
  if (!toast) return null;
  const colors = {
    success: { bg: 'var(--brand-teal)', fg: '#fff' },
    error:   { bg: 'var(--brand-coral)', fg: '#fff' },
    warn:    { bg: '#fbbf24', fg: '#78350f' },
    info:    { bg: 'var(--brand-navy)', fg: '#fff' },
  };
  const c = colors[toast.tone] || colors.success;
  return (
    <div style={{
      position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)',
      padding: '12px 20px', borderRadius: 9999,
      background: c.bg, color: c.fg,
      fontSize: 14, fontWeight: 600,
      boxShadow: 'var(--shadow-lg)', zIndex: 9999,
      maxWidth: 'calc(100vw - 32px)',
    }}>
      {toast.msg}
    </div>
  );
}

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