/* === PHYSL 210 — cloud sync + auth client (talks to the Worker in worker/index.js) ===
   Owns the session and keeps each user's study progress (physl.stats.v1 + physl.progress.v1)
   in sync with the server. localStorage stays as an offline cache; the server is the source
   of truth on login. Pushes are debounced off the 'physl-progress-dirty' event that
   data-stats.jsx fires on every save. Loaded after data-stats/data-progress, before app-shell.
   Exposes window.physlAuth (used by the login gate in app-shell.jsx). */
(function () {
  let _ver = 0;            // last server version seen (last-write-wins increment)
  let _authed = false;
  let _pushTimer = null;
  let _dirty = false;

  function api(path, opts) {
    return fetch(path, Object.assign({ credentials: 'same-origin', headers: { 'content-type': 'application/json' } }, opts || {}));
  }
  function gather() {
    return {
      stats: (typeof liveStats === 'function') ? liveStats() : null,
      progress: (typeof readProgress === 'function') ? readProgress() : null,
      version: _ver,
    };
  }

  async function me() {
    try { const r = await api('/auth/me'); if (!r.ok) return null; return (await r.json()).username || null; }
    catch (e) { return null; }
  }
  async function login(username, password) {
    try {
      const r = await api('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) });
      if (r.ok) { _authed = true; return { ok: true }; }
      let error = 'invalid'; try { error = (await r.json()).error || error; } catch (e) {}
      return { ok: false, status: r.status, error };
    } catch (e) { return { ok: false, status: 0, error: 'network' }; }
  }
  async function logout() { try { await api('/auth/logout', { method: 'POST' }); } catch (e) {} _authed = false; }
  async function changePassword(oldPassword, newPassword) {
    try {
      const r = await api('/auth/change-password', { method: 'POST', body: JSON.stringify({ oldPassword, newPassword }) });
      if (r.ok) return { ok: true };
      let error = 'error'; try { error = (await r.json()).error || error; } catch (e) {}
      return { ok: false, error };
    } catch (e) { return { ok: false, error: 'network' }; }
  }

  /* hydrate the in-memory progress from the server (server = source of truth on login).
     First-login migration: if the account is empty but THIS device has local progress,
     OFFER to upload it (opt-in confirm, so we never silently merge someone else's data). */
  async function pull() {
    try {
      const r = await api('/api/progress');
      if (!r.ok) { _authed = true; return false; }
      const j = await r.json();
      _ver = j.version | 0;
      const local = (typeof readStats === 'function') ? readStats() : null;
      const hasLocal = !!(local && local.modules && Object.keys(local.modules).length > 0);
      if (j.stats == null && hasLocal && typeof confirm === 'function' &&
          confirm("This device has existing study progress that isn't in your account yet. Add it to your account so it isn't lost?")) {
        if (typeof hydrateStats === 'function') hydrateStats(local);
        // keep local course progress too
        _authed = true;
        await pushNow();
      } else {
        if (typeof hydrateStats === 'function') hydrateStats(j.stats);
        if (typeof hydrateProgress === 'function') hydrateProgress(j.progress);
        _authed = true;
      }
      return true;
    } catch (e) { _authed = true; return false; } // offline: keep local cache, allow the app
  }

  async function pushNow() {
    if (!_authed) return;
    _dirty = false;
    try {
      const r = await api('/api/progress', { method: 'PUT', body: JSON.stringify(gather()) });
      if (r.ok) { const j = await r.json(); if (j.version != null) _ver = j.version; }
      else _dirty = true;
    } catch (e) { _dirty = true; } // retry on next dirty / unload
  }
  function schedulePush() {
    if (!_authed) return;
    _dirty = true;
    if (_pushTimer) return;
    _pushTimer = setTimeout(() => { _pushTimer = null; pushNow(); }, 1500);
  }
  function flushBeacon() {
    if (!_authed) return;
    try { if (typeof flushStats === 'function') flushStats(); } catch (e) {}
    try {
      fetch('/api/progress', { method: 'PUT', credentials: 'same-origin', headers: { 'content-type': 'application/json' }, body: JSON.stringify(gather()), keepalive: true });
    } catch (e) {}
  }

  try { window.addEventListener('physl-progress-dirty', schedulePush); } catch (e) {}
  try {
    window.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden' && _dirty) flushBeacon(); });
    window.addEventListener('pagehide', () => { if (_dirty) flushBeacon(); });
  } catch (e) {}
  window.__physlSyncBeacon = flushBeacon; // resetAllProgress() calls this before reloading

  window.physlAuth = { me, login, logout, changePassword, pull, flush: pushNow, get authed() { return _authed; } };
})();
