// auth.jsx — email/password accounts + full data sync for the Chrono beta.
//
// Talks to the accounts backend (same origin, behind Caddy):
//   POST /api/auth/signup   POST /api/auth/login   GET /api/auth/me   POST /api/auth/logout
//   GET  /api/sync          PUT  /api/sync
//
// Logged OUT  -> the app behaves exactly as before (local-only, in-memory).
// Logged IN   -> on login we pull the user's events/categories/settings; on any
//                change we debounce-push them back. Signup seeds the server with
//                whatever the user already has locally so nothing is lost.
//
// The session token lives in localStorage under 'chrono.auth.v1'. Beta keeps
// every account on the Pro plan.

(function () {
  const BASE      = (typeof window !== 'undefined' && window.CHRONO_API_BASE) || '';
  const AUTH_KEY  = 'chrono.auth.v1';
  const SKIP_KEY  = 'chrono.auth.skip.v1';
  const DEBOUNCE  = 1500;

  // ── State ────────────────────────────────────────────────────────────
  let session = null;            // { token, user:{id,email,displayName,isPro} }
  try { const raw = localStorage.getItem(AUTH_KEY); if (raw) session = JSON.parse(raw); } catch (_) {}
  let skipped = false;
  try { skipped = localStorage.getItem(SKIP_KEY) === '1'; } catch (_) {}

  let status = 'idle';           // 'idle' | 'syncing' | 'error'
  const listeners = new Set();
  const notify = () => listeners.forEach(cb => { try { cb(); } catch (_) {} });

  function persist() {
    try {
      if (session) localStorage.setItem(AUTH_KEY, JSON.stringify(session));
      else localStorage.removeItem(AUTH_KEY);
    } catch (_) {}
  }

  // ── HTTP ─────────────────────────────────────────────────────────────
  async function api(path, { method = 'GET', body } = {}) {
    const headers = { 'Accept': 'application/json' };
    if (body !== undefined) headers['Content-Type'] = 'application/json';
    if (session && session.token) headers['Authorization'] = `Bearer ${session.token}`;
    const res = await fetch(`${BASE}${path}`, {
      method, headers, body: body !== undefined ? JSON.stringify(body) : undefined,
    });
    let data = null;
    try { data = await res.json(); } catch (_) {}
    if (!res.ok) {
      const err = new Error((data && data.error) || `HTTP ${res.status}`);
      err.status = res.status;
      throw err;
    }
    return data;
  }

  // ── Sync engine ──────────────────────────────────────────────────────
  let suspend = false;           // guard against pull -> change -> push loops
  let unsubs = [];
  let timer = null;

  function applyAccountToProfile() {
    if (!session || !session.user) return;
    const u = session.user;
    try {
      window.setProfile && window.setProfile({
        email: u.email || undefined,
        name: u.displayName || undefined,
        plan: 'Pro',             // Beta: every account is Pro.
      });
    } catch (_) {}
  }

  async function pullAll() {
    if (!session) return;
    status = 'syncing'; notify();
    try {
      const data = await api('/api/sync');
      suspend = true;
      try {
        window.replaceAllEvents && window.replaceAllEvents(data.events || []);
        window.replaceAllCategories && window.replaceAllCategories(data.categories || []);
        if (data.settings && typeof data.settings === 'object') {
          window.setSettings && window.setSettings(data.settings);
        }
      } finally {
        // Let the synchronous change notifications flush before re-enabling push.
        setTimeout(() => { suspend = false; }, 0);
      }
      applyAccountToProfile();
      status = 'idle'; notify();
    } catch (err) {
      status = 'error'; notify();
      if (err.status === 401) await logout();
    }
  }

  function buildSnapshot() {
    const events = (window.exportAllEvents && window.exportAllEvents()) || [];
    const categories = (window.CATEGORIES || []).map(c => ({ id: c.id, name: c.name, color: c.color }));
    const settings = (window.settingsStore && window.settingsStore.get && window.settingsStore.get()) || {};
    return { events, categories, settings };
  }

  async function pushAll() {
    if (!session) return;
    status = 'syncing'; notify();
    try {
      await api('/api/sync', { method: 'PUT', body: buildSnapshot() });
      status = 'idle'; notify();
    } catch (err) {
      status = 'error'; notify();
      if (err.status === 401) await logout();
    }
  }

  function schedulePush() {
    if (suspend || !session) return;
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => { timer = null; pushAll(); }, DEBOUNCE);
  }

  function wirePush() {
    unwirePush();
    if (window.subscribeEvents)     unsubs.push(window.subscribeEvents(schedulePush));
    if (window.subscribeCategories) unsubs.push(window.subscribeCategories(schedulePush));
    if (window.settingsStore && window.settingsStore.subscribe) {
      unsubs.push(window.settingsStore.subscribe(schedulePush));
    }
  }
  function unwirePush() {
    unsubs.forEach(fn => { try { fn(); } catch (_) {} });
    unsubs = [];
    if (timer) { clearTimeout(timer); timer = null; }
  }

  // ── Public API ───────────────────────────────────────────────────────
  async function signup({ email, password, displayName }) {
    const data = await api('/api/auth/signup', { method: 'POST', body: { email, password, displayName } });
    session = { token: data.token, user: data.user };
    persist();
    try { localStorage.removeItem(SKIP_KEY); } catch (_) {} skipped = false;
    applyAccountToProfile();
    wirePush();
    // New account: seed the server with whatever the user already has locally.
    await pushAll();
    notify();
    return data.user;
  }

  async function login({ email, password }) {
    const data = await api('/api/auth/login', { method: 'POST', body: { email, password } });
    session = { token: data.token, user: data.user };
    persist();
    try { localStorage.removeItem(SKIP_KEY); } catch (_) {} skipped = false;
    wirePush();
    await pullAll();             // existing account: server is source of truth
    notify();
    return data.user;
  }

  // Wipe the signed-in user's data from this browser (memory + localStorage) so
  // nothing leaks to the next person on a shared device, and so a subsequent
  // signup doesn't seed a new account with the previous user's schedule. Safe:
  // the data is already on the server and is re-pulled on next login.
  function clearLocalData() {
    suspend = true;
    try {
      if (window.replaceAllEvents)     window.replaceAllEvents([]);
      if (window.replaceAllCategories) window.replaceAllCategories([]);
      if (window.resetSettings)        window.resetSettings();
      if (window.setProfile)           window.setProfile({ name: 'Your name', email: 'you@chrono.app', plan: 'Pro' });
      if (window.setIntegrations)      window.setIntegrations({ google:false, icloud:false, canvas:false, outlook:false, canvasSchool:'', canvasCourses:[] });
      if (window.clearCanvasEvents)    window.clearCanvasEvents();
    } catch (_) {}
    setTimeout(() => { suspend = false; }, 0);
  }

  async function logout() {
    if (timer) { clearTimeout(timer); timer = null; }
    try { if (session) await api('/api/auth/logout', { method: 'POST' }); } catch (_) {}
    unwirePush();
    session = null;
    persist();
    clearLocalData();
    status = 'idle';
    notify();
  }

  function skip() { try { localStorage.setItem(SKIP_KEY, '1'); } catch (_) {} skipped = true; notify(); }

  const chronoAuth = {
    isAuthed: () => !!(session && session.token),
    getUser:  () => (session ? session.user : null),
    getStatus: () => status,
    isSkipped: () => skipped,
    signup, login, logout, skip,
    // React hook: re-render on any auth/sync state change.
    use: () => {
      const [, setTick] = React.useState(0);
      React.useEffect(() => {
        const cb = () => setTick(t => t + 1);
        listeners.add(cb);
        return () => listeners.delete(cb);
      }, []);
      return chronoAuth;
    },
  };
  window.chronoAuth = chronoAuth;

  // ── Resume an existing session on load ───────────────────────────────
  if (session && session.token) {
    api('/api/auth/me')
      .then(d => { session.user = d.user; persist(); applyAccountToProfile(); wirePush(); return pullAll(); })
      .catch(err => { if (err.status === 401) logout(); });
  }

  // ── AuthGate UI ──────────────────────────────────────────────────────
  // Full-screen login/signup overlay. Hidden once the user is authenticated
  // or has chosen to continue without an account.
  // Props:
  //   embedded — when true, the gate is rendered as the first step of the
  //     onboarding flow (it never auto-hides on its own and calls onDone()
  //     once the user has signed in / signed up / chosen to skip).
  //   onDone   — advance callback for embedded mode.
  function AuthGate({ embedded = false, onDone } = {}) {
    const auth = window.chronoAuth.use();
    const [mode, setMode] = React.useState('login'); // 'login' | 'signup'
    const [email, setEmail] = React.useState('');
    const [name, setName]   = React.useState('');
    const [pw, setPw]       = React.useState('');
    const [busy, setBusy]   = React.useState(false);
    const [err, setErr]     = React.useState('');

    // Standalone gate auto-hides once resolved; embedded gate is driven by the
    // onboarding orchestrator instead (so it can't overlap the tutorial).
    if (!embedded && (auth.isAuthed() || auth.isSkipped())) return null;

    const finish = () => { if (embedded && onDone) onDone(); };

    const submit = async (e) => {
      e.preventDefault();
      setErr(''); setBusy(true);
      try {
        if (mode === 'signup') await auth.signup({ email, password: pw, displayName: name });
        else await auth.login({ email, password: pw });
        finish();
      } catch (ex) {
        setErr(ex.message || 'Something went wrong.');
      } finally { setBusy(false); }
    };

    const C = { bg:'#06070C', card:'#0e1018', line:'rgba(255,255,255,0.08)', text:'#eef1f8',
                faint:'rgba(238,241,248,0.55)', accent:'#6366f1', accentText:'#fff', danger:'#f87171' };
    const input = { width:'100%', boxSizing:'border-box', background:'#15182280', color:C.text,
      border:`1px solid ${C.line}`, borderRadius:10, padding:'11px 13px', fontSize:14, outline:'none', marginTop:6 };
    const label = { fontSize:11, fontWeight:700, letterSpacing:'0.12em', textTransform:'uppercase', color:C.faint };

    return (
      <div style={{ position:'fixed', inset:0, zIndex: embedded ? 2147483647 : 9999, background:C.bg,
        display:'flex', alignItems:'center', justifyContent:'center',
        fontFamily:'-apple-system, system-ui, sans-serif', padding:20 }}>
        <div style={{ width:360, maxWidth:'92vw', background:C.card, border:`1px solid ${C.line}`,
          borderRadius:18, padding:'30px 28px', boxShadow:'0 24px 80px rgba(0,0,0,0.55)' }}>
          <div style={{ fontSize:24, fontWeight:800, letterSpacing:'-0.02em', color:C.text }}>Chrono</div>
          <div style={{ fontSize:13.5, color:C.faint, marginTop:6 }}>
            {mode === 'signup' ? 'Create an account to sync across devices.' : 'Sign in to sync your schedule.'}
          </div>

          <div style={{ display:'flex', gap:6, margin:'20px 0 18px', background:'#15182280',
            border:`1px solid ${C.line}`, borderRadius:10, padding:4 }}>
            {['login','signup'].map(m => (
              <button key={m} type="button" onClick={()=>{ setMode(m); setErr(''); }} style={{
                flex:1, padding:'8px 0', borderRadius:7, border:'none', cursor:'pointer',
                fontSize:13, fontWeight:700, color: mode===m ? C.accentText : C.faint,
                background: mode===m ? C.accent : 'transparent' }}>
                {m === 'login' ? 'Log in' : 'Sign up'}
              </button>
            ))}
          </div>

          <form onSubmit={submit}>
            {mode === 'signup' && (
              <label style={{ display:'block', marginBottom:14 }}>
                <span style={label}>Name</span>
                <input style={input} type="text" value={name} autoComplete="name"
                  onChange={e=>setName(e.target.value)} placeholder="Your name" />
              </label>
            )}
            <label style={{ display:'block', marginBottom:14 }}>
              <span style={label}>Email</span>
              <input style={input} type="email" value={email} autoComplete="email" required
                onChange={e=>setEmail(e.target.value)} placeholder="you@example.com" />
            </label>
            <label style={{ display:'block', marginBottom:6 }}>
              <span style={label}>Password</span>
              <input style={input} type="password" value={pw}
                autoComplete={mode==='signup' ? 'new-password' : 'current-password'} required
                onChange={e=>setPw(e.target.value)} placeholder={mode==='signup' ? 'At least 8 characters' : '••••••••'} />
            </label>

            {err && <div style={{ color:C.danger, fontSize:12.5, marginTop:10 }}>{err}</div>}

            <button type="submit" disabled={busy} style={{ width:'100%', marginTop:18, padding:'12px 0',
              borderRadius:10, border:'none', cursor: busy?'default':'pointer', fontSize:14, fontWeight:700,
              color:C.accentText, background:C.accent, opacity: busy?0.7:1 }}>
              {busy ? 'Please wait…' : (mode==='signup' ? 'Create account' : 'Log in')}
            </button>
          </form>

          <button type="button" onClick={()=>{ auth.skip(); finish(); }} style={{ width:'100%', marginTop:14,
            background:'none', border:'none', color:C.faint, fontSize:12.5, cursor:'pointer' }}>
            Continue without an account
          </button>
        </div>
      </div>
    );
  }
  window.AuthGate = AuthGate;
})();
