/* supabase.jsx — optional cloud sync via the user's own Supabase project.
 *
 * Design notes:
 *   - The user provides their own Supabase URL + anon key (Settings → Cloud sync).
 *     We never hold anyone's data on infrastructure we control.
 *   - All API keys (Anthropic, OpenAI, etc.) are encrypted client-side with a
 *     passphrase the user sets, before being stored in Supabase. The encryption
 *     key is derived with PBKDF2 from the passphrase and never leaves the browser.
 *   - Schema is in setup-supabase.sql; user pastes it once into the Supabase
 *     SQL editor on first connect.
 *   - When connected, the active profile is mirrored to Supabase. We keep
 *     localStorage as a fast cache; on sign-in from another device, we pull.
 *
 * If you never connect Supabase, the app works exactly as before (localStorage only).
 */

const SUPA_KEY = 'aiso:supabase:v1';

function loadSupa() {
  try { return JSON.parse(localStorage.getItem(SUPA_KEY) || '{}'); }
  catch { return {}; }
}
function saveSupa(patch) {
  const cur = loadSupa();
  const next = { ...cur, ...patch };
  localStorage.setItem(SUPA_KEY, JSON.stringify(next));
  window.dispatchEvent(new CustomEvent('aiso:supa-changed'));
  return next;
}
function clearSupa() {
  localStorage.removeItem(SUPA_KEY);
  window.dispatchEvent(new CustomEvent('aiso:supa-changed'));
}

/* ---------- Web Crypto: PBKDF2 → AES-GCM ---------- */
async function deriveKey(passphrase, salt) {
  const enc = new TextEncoder();
  const base = await crypto.subtle.importKey(
    'raw', enc.encode(passphrase), 'PBKDF2', false, ['deriveKey']
  );
  return crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt, iterations: 250_000, hash: 'SHA-256' },
    base,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
  );
}

async function encryptString(plaintext, passphrase) {
  const enc = new TextEncoder();
  const salt = crypto.getRandomValues(new Uint8Array(16));
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const key = await deriveKey(passphrase, salt);
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    enc.encode(plaintext)
  );
  // Pack: base64(salt | iv | ciphertext)
  const packed = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
  packed.set(salt, 0);
  packed.set(iv, salt.length);
  packed.set(new Uint8Array(ciphertext), salt.length + iv.length);
  return 'v1:' + btoa(String.fromCharCode(...packed));
}

async function decryptString(blob, passphrase) {
  if (!blob || !blob.startsWith('v1:')) throw new Error('not encrypted');
  const packed = Uint8Array.from(atob(blob.slice(3)), c => c.charCodeAt(0));
  const salt = packed.slice(0, 16);
  const iv = packed.slice(16, 28);
  const ct = packed.slice(28);
  const key = await deriveKey(passphrase, salt);
  const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);
  return new TextDecoder().decode(plain);
}

/* ---------- Supabase REST + Auth (no SDK required) ----------
 * Using REST keeps us lean — no extra script tag, no version-pinning surprises.
 */

function supaUrl(state, path) {
  return state.url.replace(/\/$/, '') + path;
}

async function supaFetch(state, path, opts = {}) {
  const headers = {
    'apikey': state.anonKey,
    'Authorization': `Bearer ${state.session?.access_token || state.anonKey}`,
    'Content-Type': 'application/json',
    ...(opts.headers || {}),
  };
  if (opts.body && typeof opts.body !== 'string') opts.body = JSON.stringify(opts.body);
  const r = await fetch(supaUrl(state, path), { ...opts, headers });
  if (!r.ok) {
    let msg = `HTTP ${r.status}`;
    try { const data = await r.json(); if (data.error || data.msg || data.message) msg += ': ' + (data.error_description || data.error || data.msg || data.message); } catch {}
    throw new Error(msg);
  }
  if (r.status === 204) return null;
  const ct = r.headers.get('content-type') || '';
  if (ct.includes('application/json')) return r.json();
  return r.text();
}

async function supaPing(url, anonKey) {
  // hit /auth/v1/settings — public, no JWT needed
  const r = await fetch(url.replace(/\/$/, '') + '/auth/v1/settings', {
    headers: { 'apikey': anonKey },
  });
  if (!r.ok) throw new Error(`HTTP ${r.status} — check URL + anon key`);
  return r.json();
}

async function supaSignUp(state, email, password) {
  const data = await supaFetch(state, '/auth/v1/signup', {
    method: 'POST',
    body: { email, password },
  });
  if (data?.access_token) {
    saveSupa({ session: data, user: data.user });
  }
  return data;
}

async function supaSignIn(state, email, password) {
  const data = await supaFetch(state, '/auth/v1/token?grant_type=password', {
    method: 'POST',
    body: { email, password },
  });
  saveSupa({ session: data, user: data.user });
  return data;
}

async function supaSignOut() {
  const state = loadSupa();
  if (!state.session) return;
  try {
    await supaFetch(state, '/auth/v1/logout', { method: 'POST' });
  } catch {}
  saveSupa({ session: null, user: null });
}

async function supaSendMagicLink(state, email) {
  return supaFetch(state, '/auth/v1/otp', {
    method: 'POST',
    body: { email, create_user: true },
  });
}

/* ---------- central account system: bootstrap config + email-code (OTP) + recovery ---------- */
// Pull AISO's central Supabase config once (anon key is public, RLS protects data).
async function supaBootstrap() {
  const cur = loadSupa();
  if (cur.url && cur.anonKey) return { configured: true };
  try {
    const cfg = await (await fetch('/api/supabase-config')).json();
    if (cfg.configured) { saveSupa({ url: cfg.url, anonKey: cfg.anonKey }); return { configured: true }; }
  } catch {}
  return { configured: false };
}

// Send a 6-digit login code (Supabase OTP). Works for new + existing users.
async function supaSendCode(email) {
  return supaFetch(loadSupa(), '/auth/v1/otp', { method: 'POST', body: { email, create_user: true } });
}

// Verify the emailed code → establishes a session. Tries email-OTP first, then the
// signup-confirmation type (new/unconfirmed users get the code in the signup email).
async function supaVerifyCode(email, token) {
  let data;
  try {
    data = await supaFetch(loadSupa(), '/auth/v1/verify', { method: 'POST', body: { type: 'email', email, token } });
  } catch (e) {
    data = await supaFetch(loadSupa(), '/auth/v1/verify', { method: 'POST', body: { type: 'signup', email, token } });
  }
  if (data?.access_token) saveSupa({ session: data, user: data.user });
  return data;
}

// Password reset email.
async function supaRecover(email) {
  return supaFetch(loadSupa(), '/auth/v1/recover', { method: 'POST', body: { email } });
}

// Set / change the signed-in user's password.
async function supaSetPassword(password) {
  return supaFetch(loadSupa(), '/auth/v1/user', { method: 'PUT', body: { password } });
}

function supaCurrentUser() { return loadSupa().session?.user || null; }
function supaIsAuthed() { const s = loadSupa(); return !!s.session?.access_token; }

/* ---------- table operations (uses PostgREST under /rest/v1) ---------- */

async function supaSaveProfile(state, profileData) {
  if (!state.session?.user?.id) return null;
  return supaFetch(state, '/rest/v1/aiso_profiles?on_conflict=user_id', {
    method: 'POST',
    headers: { 'Prefer': 'resolution=merge-duplicates,return=representation' },
    body: { user_id: state.session.user.id, data: profileData, updated_at: new Date().toISOString() },
  });
}

async function supaLoadProfile(state) {
  if (!state.session?.user?.id) return null;
  const rows = await supaFetch(state, `/rest/v1/aiso_profiles?user_id=eq.${state.session.user.id}&select=data,updated_at`);
  return rows?.[0] || null;
}

/* ---------- sync logic ---------- */

let syncTimer = null;
async function maybeSyncProfileToCloud(profileObj) {
  const state = loadSupa();
  if (!state.url || !state.anonKey || !state.session) return;
  clearTimeout(syncTimer);
  syncTimer = setTimeout(async () => {
    try {
      await supaSaveProfile(state, profileObj);
      saveSupa({ lastSync: Date.now() });
    } catch (e) {
      console.warn('Supabase sync failed:', e.message);
    }
  }, 1500);
}

async function pullProfileFromCloud() {
  const state = loadSupa();
  if (!state.url || !state.anonKey || !state.session) return null;
  try {
    const row = await supaLoadProfile(state);
    return row?.data || null;
  } catch (e) {
    console.warn('Supabase pull failed:', e.message);
    return null;
  }
}

/* ---------- React: Cloud Connector for Settings ---------- */
function SupabaseConnector({ profile }) {
  const [state, setState] = React.useState(() => loadSupa());
  const [step, setStep] = React.useState(() => state.url ? (state.session ? 'connected' : 'sign-in') : 'configure');
  const [url, setUrl] = React.useState(state.url || '');
  const [anonKey, setAnonKey] = React.useState(state.anonKey || '');
  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [success, setSuccess] = React.useState(null);

  React.useEffect(() => {
    const onCh = () => { setState(loadSupa()); };
    window.addEventListener('aiso:supa-changed', onCh);
    return () => window.removeEventListener('aiso:supa-changed', onCh);
  }, []);

  async function testAndSave() {
    setBusy(true); setError(null); setSuccess(null);
    try {
      await supaPing(url.trim(), anonKey.trim());
      saveSupa({ url: url.trim(), anonKey: anonKey.trim() });
      setState(loadSupa());
      setSuccess('Supabase reachable. Now sign in or create an account.');
      setStep('sign-in');
    } catch (e) {
      setError(e.message);
    } finally { setBusy(false); }
  }

  async function doSignIn() {
    setBusy(true); setError(null);
    try {
      await supaSignIn(loadSupa(), email, password);
      setState(loadSupa());
      // pull existing profile if any
      const remote = await pullProfileFromCloud();
      if (remote) {
        // merge: cloud wins on sites/scans, local wins on api keys (until passphrase setup)
        localStorage.setItem('aiso:profile:v1', JSON.stringify({ ...remote }));
        window.dispatchEvent(new CustomEvent('aiso:profile-pulled'));
        setSuccess('Signed in and synced from cloud. Refresh to see your data.');
      } else {
        setSuccess('Signed in. Your local data will now sync.');
      }
      setStep('connected');
    } catch (e) { setError(e.message); }
    finally { setBusy(false); }
  }

  async function doSignUp() {
    setBusy(true); setError(null);
    try {
      const out = await supaSignUp(loadSupa(), email, password);
      setState(loadSupa());
      if (out?.user && !out?.access_token) {
        setSuccess('Check your email to confirm. Then sign in.');
      } else {
        setSuccess('Account created and signed in.');
        setStep('connected');
      }
    } catch (e) { setError(e.message); }
    finally { setBusy(false); }
  }

  async function doSignOut() {
    setBusy(true);
    try {
      await supaSignOut();
      setState(loadSupa());
      setStep('sign-in');
    } finally { setBusy(false); }
  }

  function disconnect() {
    clearSupa();
    setState({});
    setUrl(''); setAnonKey('');
    setStep('configure');
  }

  // ───────── render ─────────
  return (
    <div className="supa-panel">
      <div className="supa-head">
        <div className="supa-logo">⌬</div>
        <div className="supa-head-text">
          <h3>Supabase</h3>
          <p>Your own Postgres. Your own auth. We store nothing.</p>
        </div>
      </div>

      {step === 'configure' && (
        <div className="supa-step">
          <ol className="supa-setup">
            <li>Create a free project at <a href="https://supabase.com/dashboard/new" target="_blank" rel="noopener">supabase.com</a></li>
            <li>Settings → API → copy <strong>Project URL</strong> + <strong>anon public key</strong></li>
            <li>SQL Editor → paste the schema from <a href="https://github.com/cicero80-max/aiso/blob/main/setup-supabase.sql" target="_blank" rel="noopener">setup-supabase.sql</a> → Run</li>
            <li>Paste URL + key below.</li>
          </ol>

          <div className="supa-fields">
            <label className="field mono">
              <span className="field-label">Project URL</span>
              <input value={url} onChange={(e) => setUrl(e.target.value)} placeholder="https://xxx.supabase.co" />
            </label>
            <label className="field mono">
              <span className="field-label">Anon public key</span>
              <input value={anonKey} onChange={(e) => setAnonKey(e.target.value)} placeholder="eyJhbGciOi…" type="password" />
            </label>
          </div>

          <div className="supa-actions">
            <button className="btn btn-primary" onClick={testAndSave} disabled={!url.trim() || !anonKey.trim() || busy}>
              {busy ? 'Testing…' : 'Test & save'}
            </button>
          </div>

          {error && <div className="supa-error">{error}</div>}
          {success && <div className="supa-ok">{success}</div>}
        </div>
      )}

      {step === 'sign-in' && (
        <div className="supa-step">
          <div className="supa-status">
            <span className="db-dot live-pulse" />
            Project: <code>{url || state.url}</code>
          </div>

          <div className="supa-fields">
            <label className="field">
              <span className="field-label">Email</span>
              <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@example.com" autoComplete="email" />
            </label>
            <label className="field">
              <span className="field-label">Password</span>
              <input value={password} onChange={(e) => setPassword(e.target.value)} type="password" autoComplete="current-password" />
            </label>
          </div>

          <div className="supa-actions">
            <button className="btn btn-primary" onClick={doSignIn} disabled={!email || !password || busy}>{busy ? '…' : 'Sign in'}</button>
            <button className="btn" onClick={doSignUp} disabled={!email || !password || busy}>Create account</button>
            <button className="btn-ghost" onClick={disconnect}>Use a different project</button>
          </div>

          {error && <div className="supa-error">{error}</div>}
          {success && <div className="supa-ok">{success}</div>}
        </div>
      )}

      {step === 'connected' && state.session && (
        <div className="supa-step">
          <div className="supa-connected">
            <div className="supa-user">
              <div className="supa-avatar">{(state.user?.email || '?')[0].toUpperCase()}</div>
              <div>
                <div className="supa-user-email">{state.user?.email}</div>
                <div className="supa-subtle">
                  Project: <code>{(state.url || '').replace(/^https?:\/\//, '').split('.')[0]}</code>
                  {state.lastSync && <> · synced {timeAgoSupa(state.lastSync)}</>}
                </div>
              </div>
            </div>
            <div className="supa-actions">
              <button className="btn" onClick={async () => {
                setBusy(true);
                try {
                  await supaSaveProfile(loadSupa(), JSON.parse(localStorage.getItem('aiso:profile:v1') || '{}'));
                  saveSupa({ lastSync: Date.now() });
                  setSuccess('Pushed to cloud.');
                } catch (e) { setError(e.message); }
                finally { setBusy(false); }
              }}>Push now</button>
              <button className="btn" onClick={async () => {
                const remote = await pullProfileFromCloud();
                if (remote) {
                  localStorage.setItem('aiso:profile:v1', JSON.stringify(remote));
                  window.dispatchEvent(new CustomEvent('aiso:profile-pulled'));
                  setSuccess('Pulled from cloud. Refresh.');
                } else setError('No profile in cloud yet.');
              }}>Pull now</button>
              <button className="btn-ghost" onClick={doSignOut}>Sign out</button>
            </div>
          </div>

          <div className="supa-tips">
            <h4>What's synced</h4>
            <ul>
              <li>Sites you've added (name, domain, industry, location, competitors)</li>
              <li>Scan history (visibility-over-time chart per site)</li>
              <li>Notification preferences</li>
              <li>App settings (theme, accent)</li>
            </ul>
            <h4>What's <strong>not</strong> synced</h4>
            <ul>
              <li>Your API keys (Anthropic, OpenAI, etc.) — they remain in this browser only. Wire them again on each device. Encrypted-key sync is on the roadmap.</li>
              <li>GitHub PAT — same reasoning.</li>
            </ul>
          </div>

          {error && <div className="supa-error">{error}</div>}
          {success && <div className="supa-ok">{success}</div>}
        </div>
      )}
    </div>
  );
}

function timeAgoSupa(ts) {
  const s = Math.floor((Date.now() - ts) / 1000);
  if (s < 60) return `${s}s ago`;
  if (s < 3600) return `${Math.floor(s / 60)}m ago`;
  if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
  return `${Math.floor(s / 86400)}d ago`;
}

/* ---------- Modern account modal: email-code first, password + reset fallback ---------- */
function AuthModal({ onClose, onAuthed }) {
  // Resume the code step after a reload: if a code was sent in the last hour, reopen on it.
  const _pending = (() => { try { const p = JSON.parse(localStorage.getItem('aiso:pending-otp') || 'null'); return (p && Date.now() - p.at < 3600000) ? p : null; } catch { return null; } })();
  const [mode, setMode] = React.useState(_pending ? 'code' : 'email'); // email | code | password | reset
  const [email, setEmail] = React.useState(_pending?.email || '');
  const [code, setCode] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [notice, setNotice] = React.useState(null);
  const [sentTo, setSentTo] = React.useState(_pending?.email || '');
  const [cooldown, setCooldown] = React.useState(0);
  const codeRef = React.useRef(null);
  React.useEffect(() => { if (mode === 'code' && codeRef.current) codeRef.current.focus(); }, [mode]);
  React.useEffect(() => { if (cooldown <= 0) return; const t = setTimeout(() => setCooldown((c) => c - 1), 1000); return () => clearTimeout(t); }, [cooldown]);

  const wrap = (fn) => async (e) => { e?.preventDefault?.(); if (busy) return; setBusy(true); setError(null); setNotice(null); try { await fn(); } catch (err) {
    const msg = err.message || String(err);
    const m = /(\d+)\s*second/.exec(msg);
    if (msg.includes('429') || /only request this after|rate limit/i.test(msg) || m) { const n = m ? parseInt(m[1], 10) : 30; setCooldown(n); setError(`Too many requests — wait ${n}s, then try again.`); }
    else setError(msg);
  } finally { setBusy(false); } };
  const sendCode = wrap(async () => {
    if (!email.trim()) return;
    await supaSendCode(email.trim());
    setSentTo(email.trim()); setMode('code'); setCooldown(30);
    try { localStorage.setItem('aiso:pending-otp', JSON.stringify({ email: email.trim(), at: Date.now() })); } catch {}
  });
  const verifyCode = wrap(async () => {
    const c = code.replace(/\D/g, '');
    if (c.length < 6) { setError('Enter the 6-digit code.'); return; }
    const d = await supaVerifyCode((sentTo || email).trim(), c);
    if (d?.access_token) { try { localStorage.removeItem('aiso:pending-otp'); } catch {} onAuthed?.(); onClose?.(); } else throw new Error('That code didn’t work.');
  });
  const passwordAuth = wrap(async () => {
    if (!email.trim() || !password) return;
    try { const d = await supaSignIn(loadSupa(), email.trim(), password); if (d?.access_token) { onAuthed?.(); onClose?.(); return; } } catch (e1) {
      const d2 = await supaSignUp(loadSupa(), email.trim(), password);
      if (d2?.access_token) { onAuthed?.(); onClose?.(); return; }
      setNotice('Account created — check your email to confirm, then sign in.');
    }
  });
  const reset = wrap(async () => { if (!email.trim()) return; await supaRecover(email.trim()); setNotice('Reset link sent — check your email.'); });

  const overlay = { position: 'fixed', inset: 0, zIndex: 1000, display: 'grid', placeItems: 'center', background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(8px)', padding: 20 };
  const card = { width: '100%', maxWidth: 420, background: 'var(--bg-1, #111317)', border: '1px solid var(--line)', borderRadius: 18, padding: '28px 26px', boxShadow: '0 30px 80px rgba(0,0,0,0.5)' };
  const input = { width: '100%', padding: '13px 14px', borderRadius: 10, border: '1px solid var(--line)', background: 'var(--bg-2)', color: 'var(--fg-0)', fontSize: 15, outline: 'none', marginBottom: 10 };
  const primary = { width: '100%', padding: '13px', borderRadius: 10, border: 'none', background: 'var(--accent-grad, var(--accent))', color: '#0b0c10', fontSize: 15, fontWeight: 600, cursor: 'pointer' };
  const link = { background: 'none', border: 'none', color: 'var(--fg-2)', cursor: 'pointer', fontSize: 13, textDecoration: 'underline', padding: 0 };

  return (
    <div style={overlay}>
      <div style={card} className="fade-in">
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 18 }}>
          <span style={{ fontFamily: 'var(--serif, Georgia, serif)', fontSize: 20, letterSpacing: '0.04em' }}>AISO</span>
          <button onClick={onClose} style={{ ...link, textDecoration: 'none', fontSize: 22, lineHeight: 1 }}>×</button>
        </div>

        {mode === 'email' && (
          <form onSubmit={sendCode}>
            <h2 style={{ fontSize: 22, margin: '0 0 4px' }}>Sign in or create your account</h2>
            <p style={{ color: 'var(--fg-2)', fontSize: 14, margin: '0 0 18px' }}>We'll email you a 6-digit code — no password to remember.</p>
            <input style={input} type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@company.com" autoFocus autoComplete="email" />
            <button style={primary} disabled={busy || cooldown > 0 || !email.trim()}>{cooldown > 0 ? `Wait ${cooldown}s` : busy ? 'Sending…' : 'Continue with email →'}</button>
            <div style={{ display: 'flex', justifyContent: 'center', gap: 16, marginTop: 14, flexWrap: 'wrap' }}>
              <button type="button" style={link} onClick={() => { setSentTo(email.trim()); setMode('code'); setError(null); }}>I already have a code →</button>
              <button type="button" style={link} onClick={() => { setMode('password'); setError(null); }}>Use a password instead</button>
            </div>
          </form>
        )}

        {mode === 'code' && (
          <form onSubmit={verifyCode}>
            <h2 style={{ fontSize: 22, margin: '0 0 4px' }}>Enter your code</h2>
            <p style={{ color: 'var(--fg-2)', fontSize: 14, margin: '0 0 18px' }}>{sentTo ? <>We emailed a 6-digit code to <strong style={{ color: 'var(--fg-0)' }}>{sentTo}</strong>.</> : 'Enter the email you used and the 6-digit code we sent.'}</p>
            <input style={input} type="email" value={sentTo || email} onChange={(e) => { setSentTo(e.target.value); setEmail(e.target.value); }} placeholder="you@company.com" autoComplete="email" />
            <input ref={codeRef} style={{ ...input, fontFamily: 'var(--mono)', fontSize: 24, letterSpacing: '0.3em', textAlign: 'center' }} inputMode="numeric" maxLength={10} value={code} onChange={(e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 10))} placeholder="Enter code" />
            <button style={primary} disabled={busy || code.length < 6 || !(sentTo || email).trim()}>{busy ? 'Verifying…' : 'Verify →'}</button>
            <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 14 }}>
              <button type="button" style={link} onClick={() => { setMode('email'); setCode(''); setError(null); }}>← Back</button>
              <button type="button" style={link} onClick={sendCode} disabled={busy || cooldown > 0}>{cooldown > 0 ? `Resend in ${cooldown}s` : 'Send a new code'}</button>
            </div>
          </form>
        )}

        {mode === 'password' && (
          <form onSubmit={passwordAuth}>
            <h2 style={{ fontSize: 22, margin: '0 0 4px' }}>Sign in</h2>
            <p style={{ color: 'var(--fg-2)', fontSize: 14, margin: '0 0 18px' }}>New here? Entering a password creates your account.</p>
            <input style={input} type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@company.com" autoComplete="email" />
            <input style={input} type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" autoComplete="current-password" />
            <button style={primary} disabled={busy || !email.trim() || !password}>{busy ? '…' : 'Continue →'}</button>
            <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 14 }}>
              <button type="button" style={link} onClick={() => { setMode('email'); setError(null); }}>← Email code</button>
              <button type="button" style={link} onClick={() => { setMode('reset'); setError(null); }}>Forgot password?</button>
            </div>
          </form>
        )}

        {mode === 'reset' && (
          <form onSubmit={reset}>
            <h2 style={{ fontSize: 22, margin: '0 0 4px' }}>Reset your password</h2>
            <p style={{ color: 'var(--fg-2)', fontSize: 14, margin: '0 0 18px' }}>We'll email you a reset link.</p>
            <input style={input} type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@company.com" autoComplete="email" />
            <button style={primary} disabled={busy || !email.trim()}>{busy ? 'Sending…' : 'Send reset link'}</button>
            <div style={{ textAlign: 'center', marginTop: 14 }}><button type="button" style={link} onClick={() => { setMode('password'); setError(null); }}>← Back</button></div>
          </form>
        )}

        {error && <div style={{ marginTop: 14, color: '#f0746e', fontSize: 13 }}>{error}</div>}
        {notice && <div style={{ marginTop: 14, color: 'var(--good, #5ad08a)', fontSize: 13 }}>{notice}</div>}
      </div>
    </div>
  );
}

Object.assign(window, {
  supaLoad: loadSupa,
  supaSave: saveSupa,
  supaBootstrap,
  supaSendCode,
  supaVerifyCode,
  supaRecover,
  supaSetPassword,
  supaCurrentUser,
  supaIsAuthed,
  AuthModal,
  supaSignIn,
  supaSignUp,
  supaSignOut,
  supaSaveProfile,
  supaLoadProfile,
  supaPullProfile: pullProfileFromCloud,
  supaMaybeSync: maybeSyncProfileToCloud,
  supaEncrypt: encryptString,
  supaDecrypt: decryptString,
  SupabaseConnector,
});
