// src/features/student/ui/student-onboarding.jsx
//
// The multi-step signup wizard a student goes through the first time
// they're invited. Extracted from student-dashboard.jsx for size
// (was lines 278-622 there).
//
// Depends on (via window): COUNTRIES, DEFAULT_COUNTRY, phoneDigits,
// newRandomPassword, persistPhoneCred, AvailabilityStep.
// Public: window.StudentOnboarding.

const { COUNTRIES, DEFAULT_COUNTRY, phoneDigits, newRandomPassword,
        persistPhoneCred, AvailabilityStep } = window;

// localStorage key + 7-day TTL for in-progress wizard state. Lets the
// student bail mid-wizard (X button, accidental tab close, switching
// apps on mobile) and pick up exactly where they left off next time
// they hit Get Started. Cleared on successful signup.
const SIGNUP_PROGRESS_KEY = 'mastery_signup_progress_v1';
const SIGNUP_PROGRESS_TTL_MS = 7 * 24 * 60 * 60 * 1000;

function readSavedProgress() {
  try {
    const raw = window.localStorage.getItem(SIGNUP_PROGRESS_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== 'object') return null;
    if (typeof parsed.savedAt !== 'number') return null;
    if (Date.now() - parsed.savedAt > SIGNUP_PROGRESS_TTL_MS) return null;
    return parsed;
  } catch (e) { return null; }
}

function writeSavedProgress(state) {
  try {
    window.localStorage.setItem(SIGNUP_PROGRESS_KEY, JSON.stringify({
      ...state, savedAt: Date.now(),
    }));
  } catch (e) { /* private mode etc — non-fatal */ }
}

function clearSavedProgress() {
  try { window.localStorage.removeItem(SIGNUP_PROGRESS_KEY); } catch (e) {}
}

const StudentOnboarding = ({ prefillPhone, onComplete, onBack }) => {
  // If there's a recent saved-progress entry, restore the step + form so
  // the student doesn't have to type everything again. prefillPhone (only
  // populated via the legacy /login → "new student?" path) wins over
  // saved state because it represents an explicit fresh handoff.
  const saved = !prefillPhone ? readSavedProgress() : null;

  const [step, setStep] = React.useState(saved && saved.step >= 1 && saved.step <= 5 ? saved.step : 1);
  const defaultAvail = Object.fromEntries([0,1,2,3,4,5,6].map(i => [i, { enabled:false, blocks:[{from:9,to:17}] }]));

  // Initial country/local split — accept either a plain digit string or a
  // "+<dial>..." prefill from the legacy login screen.
  const initial = (() => {
    const seed = String(prefillPhone || '');
    const det = detectCountryFromInput(seed);
    if (det) {
      const max = det.country.max;
      return { country: det.country, phoneLocal: phoneDigits(det.rest).slice(0, max) };
    }
    return { country: DEFAULT_COUNTRY, phoneLocal: phoneDigits(seed).slice(0, DEFAULT_COUNTRY.max) };
  })();

  const [form, setForm] = React.useState(() => {
    const base = {
      name:'', email:'', topic:'',
      country: initial.country,
      phoneLocal: initial.phoneLocal,
      availability:defaultAvail, availabilityNote:'',
      // Default to the browser's IANA timezone. The student can flip it
      // via the TimezoneBadge on step 5; we persist the final value to
      // profiles.timezone in finish() so the booking + reminder flows
      // show the right wall-clock to them.
      timezone: (window.MASTERY && window.Calendar.time.detect && window.Calendar.time.detect()) || 'America/New_York',
    };
    if (!saved) return base;
    const savedCountry = saved.countryCode
      ? (COUNTRIES.find(c => c.code === saved.countryCode) || base.country)
      : base.country;
    return {
      name:             typeof saved.name === 'string' ? saved.name : base.name,
      email:            typeof saved.email === 'string' ? saved.email : base.email,
      topic:            typeof saved.topic === 'string' ? saved.topic : base.topic,
      country:          savedCountry,
      phoneLocal:       typeof saved.phoneLocal === 'string' ? saved.phoneLocal : base.phoneLocal,
      availability:     saved.availability && typeof saved.availability === 'object' ? saved.availability : base.availability,
      availabilityNote: typeof saved.availabilityNote === 'string' ? saved.availabilityNote : base.availabilityNote,
      timezone:         typeof saved.timezone === 'string' ? saved.timezone : base.timezone,
    };
  });
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  // When signUp succeeds but no session is returned (project requires
  // email confirmation), we land on this screen instead of /dashboard.
  // Set to the email we just signed up with.
  const [pendingConfirmEmail, setPendingConfirmEmail] = React.useState('');
  // Save-and-exit modal state: shown when student clicks X mid-wizard with
  // an email already entered. They confirm + solve a captcha, we
  // signInWithOtp with whatever partial fields they've filled, the trigger
  // creates their profile + student_info rows, and they get a magic-link
  // email to come back.
  const [showSaveModal, setShowSaveModal] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [saveError, setSaveError] = React.useState('');
  const set = (k,v) => setForm(f => ({...f,[k]:v}));

  // Signed-in mode: when the student comes back via the magic-link sent
  // from a save-and-exit, they land on /?page=dashboard with a real
  // session. If the banner there pushes them back to this wizard to
  // finish, we want it to UPDATE their existing rows on submit instead
  // of calling supa.auth.signUp (which would 422 — user already exists).
  // We read auth from window.useAuth — same pattern other dashboards use.
  const authHook = (window.useAuth ? window.useAuth() : null);
  const signedInUser = authHook && authHook.user;
  const signedInProfile = authHook && authHook.profile;

  // When the wizard mounts already-signed-in and we don't have saved
  // progress for THIS user, prime the form from server-side state so
  // the student doesn't have to retype the fields they already submitted
  // before X-press. Local saved-progress (from THIS device) wins because
  // it represents their latest in-progress edits.
  React.useEffect(() => {
    if (!signedInUser || saved) return;
    let cancelled = false;
    (async () => {
      try {
        const sessEmail = (signedInUser.email || '').toLowerCase();
        const profName  = (signedInProfile && signedInProfile.full_name) || '';
        const profTz    = (signedInProfile && signedInProfile.timezone) || '';
        const { data: si } = await window.supa.from('student_info')
          .select('phone, topics_of_interest, availability, availability_note')
          .eq('user_id', signedInUser.id).maybeSingle();
        if (cancelled) return;
        const phoneStr = (si && si.phone) || '';
        // Strip leading "+<dial> " or similar; the COUNTRIES list is the
        // source of truth for what to display in the country dropdown.
        const det = detectCountryFromInput(phoneStr);
        setForm(f => ({
          ...f,
          name: profName || f.name,
          email: sessEmail || f.email,
          country: det ? det.country : f.country,
          phoneLocal: det ? phoneDigits(det.rest).slice(0, det.country.max) : (phoneDigits(phoneStr).slice(0, f.country.max) || f.phoneLocal),
          topic: (si && si.topics_of_interest && si.topics_of_interest[0]) || f.topic,
          availability: (si && si.availability && typeof si.availability === 'object') ? si.availability : f.availability,
          availabilityNote: (si && si.availability_note) || f.availabilityNote,
          timezone: profTz || f.timezone,
        }));
        // Skip ahead to first incomplete step if user came in from a
        // half-finished signup.
        const startStep =
          !profName ? 1 :
          !phoneStr ? 2 :
          !sessEmail ? 3 :
          !(si && si.topics_of_interest && si.topics_of_interest.length) ? 4 :
          !(si && si.availability && Object.values(si.availability).some(d => d && d.enabled)) ? 5 : 5;
        setStep(startStep);
      } catch (e) { /* non-fatal — wizard still works from defaults */ }
    })();
    return () => { cancelled = true; };
  }, [signedInUser?.id]);

  // Persist wizard progress on every form/step change. Pressing X, closing
  // the tab, or switching apps mid-flow no longer drops the student to
  // step 1 — next visit auto-resumes from saved state (7-day TTL).
  // Skip persisting if NOTHING has been entered yet (no name, no email)
  // to avoid stamping a fresh-but-empty entry over a previous real one.
  React.useEffect(() => {
    const hasAnyInput = (form.name && form.name.trim()) || (form.email && form.email.trim()) ||
                        (form.phoneLocal && form.phoneLocal.length > 0) || (form.topic && form.topic.length > 0);
    if (!hasAnyInput && step === 1) return;
    writeSavedProgress({
      step,
      name: form.name,
      email: form.email,
      topic: form.topic,
      countryCode: form.country && form.country.code,
      phoneLocal: form.phoneLocal,
      availability: form.availability,
      availabilityNote: form.availabilityNote,
      timezone: form.timezone,
    });
  }, [step, form.name, form.email, form.topic, form.country, form.phoneLocal,
      form.availability, form.availabilityNote, form.timezone]);

  // What ends up in student_info.phone and feeds the synthetic-cred cache.
  const canonicalPhone = formatPhoneDisplay(form.country, form.phoneLocal);
  const phoneIsOk = phoneOk(form.country, form.phoneLocal);

  // Country dropdown + formatted input as a single composed field.
  const onPhoneInput = (raw) => {
    // Detect a typed "+CC ..." prefix and swap the dropdown to match.
    const det = detectCountryFromInput(raw);
    if (det) {
      const local = phoneDigits(det.rest).slice(0, det.country.max);
      setForm(f => ({ ...f, country: det.country, phoneLocal: local }));
      return;
    }
    const local = phoneDigits(raw).slice(0, form.country.max);
    set('phoneLocal', local);
  };
  const onCountryChange = (code) => {
    const c = COUNTRIES.find(x => x.code === code) || DEFAULT_COUNTRY;
    setForm(f => ({ ...f, country: c, phoneLocal: f.phoneLocal.slice(0, c.max) }));
  };

  const phoneFieldStyle = { ...stdFieldStyle, paddingLeft:14 };
  const phoneBody = (
    <div style={{ display:'flex', gap:10, alignItems:'stretch' }}>
      <select
        value={form.country.code}
        onChange={e => onCountryChange(e.target.value)}
        aria-label="Country code"
        style={{
          padding:'18px 12px', borderRadius:14, border:'2.5px solid oklch(88% 0.015 265)',
          fontSize:17, fontFamily:"'Plus Jakarta Sans', sans-serif",
          color:'oklch(18% 0.04 265)', background:'#fff',
          outline:'none', boxSizing:'border-box', cursor:'pointer',
          minWidth:118, flexShrink:0,
        }}>
        {COUNTRIES.map(c => (
          <option key={c.code} value={c.code}>{c.flag} +{c.dial}</option>
        ))}
      </select>
      <input
        autoFocus
        type="tel"
        inputMode="tel"
        value={formatLocal(form.phoneLocal, form.country.fmt)}
        onChange={e => onPhoneInput(e.target.value)}
        onKeyDown={e => e.key==='Enter' && phoneIsOk && setStep(3)}
        placeholder={form.country.fmt === 'us' ? '212-867-5309' : '7700 900 123'}
        style={{ ...phoneFieldStyle, flex:1, minWidth:0 }}
      />
    </div>
  );

  const steps = [
    {
      q:"What's your full name?",
      hint:"We'll use this for your profile.",
      body: <input autoFocus value={form.name} onChange={e => set('name',e.target.value)} onKeyDown={e => e.key==='Enter' && form.name.trim() && setStep(2)} placeholder="Your full name" style={stdFieldStyle} />,
      ok: form.name.trim().length > 1,
    },
    {
      q:'Your phone number?',
      hint:"We'll use this for booking reminders.",
      body: phoneBody,
      ok: phoneIsOk,
    },
    {
      q:'Your email address?',
      hint:"This is how you'll log in. We'll email a magic link.",
      body: <input autoFocus type="email" value={form.email} onChange={e => set('email',e.target.value)} onKeyDown={e => e.key==='Enter' && isValidEmail(form.email) && setStep(4)} placeholder="your@email.com" style={stdFieldStyle} />,
      ok: isValidEmail(form.email),
    },
    {
      q:'What do you want to learn?',
      hint:'Pick one — you can always change it.',
      body: (
        <div style={{ display:'flex', flexWrap:'wrap', gap:10, maxHeight:280, overflowY:'auto', paddingRight:4 }}>
          {STD_SUBJECTS.map(s => (
            <button key={s} type="button" onClick={() => set('topic',s)} style={{
              padding:'13px 20px', borderRadius:30,
              border:`2.5px solid ${form.topic===s ? 'oklch(22% 0.06 265)' : 'oklch(86% 0.02 265)'}`,
              background: form.topic===s ? 'oklch(22% 0.06 265)' : '#fff',
              color: form.topic===s ? '#fff' : 'oklch(38% 0.04 265)',
              fontSize:15, fontWeight:600, cursor:'pointer',
              fontFamily:"'Plus Jakarta Sans', sans-serif", transition:'all 0.13s',
            }}>{s}</button>
          ))}
        </div>
      ),
      ok: form.topic.length > 0,
    },
    {
      q: 'When are you available?',
      hint: 'Confirm your time zone, then toggle each day on and pick a time range.',
      body: (
        <>
          {/* TimezoneBadge is defined in availability-picker.jsx. Passing
              onChange makes it an interactive pill — the student can
              flip zones and the days/times above are interpreted in the
              selected zone. */}
          {window.TimezoneBadge && (
            <div style={{ marginBottom:14 }}>
              <window.TimezoneBadge
                value={form.timezone}
                onChange={tz => set('timezone', tz)}
              />
            </div>
          )}
          <AvailabilityStep
            avail={form.availability}
            note={form.availabilityNote}
            onChangeAvail={a => set('availability',a)}
            onChangeNote={n => set('availabilityNote',n)}
          />
        </>
      ),
      ok: Object.values(form.availability).some(d => d.enabled && (d.blocks||[]).length > 0),
    },
  ];

  const cur = steps[step-1];
  const captcha = window.useCaptcha ? window.useCaptcha() : { token: null, widget: null, required: false };
  const isLastStep = step === steps.length;
  const captchaToken = captcha.token;

  // (auth) — sign up via the v33 trigger.
  //
  // Email confirmation is enforced project-wide (mailer_autoconfirm =
  // false). That means supa.auth.signUp returns data.user but NO session,
  // and any client-side `from('profiles').upsert(...)` we do here gets
  // denied by RLS with 42501. Instead, we pack everything the wizard
  // captured (name, phone, topic, availability) into raw_user_meta_data;
  // the `mastery_on_auth_user_created` trigger in supabase/v33-schema.sql
  // reads it and creates profiles + student_info server-side.
  //
  // After signUp succeeds we either:
  //   - have a session (rare — would mean confirmation was disabled, or
  //     the user clicked through this on a device where they were already
  //     signed in to a different account) → reload AUTH, go to dashboard.
  //   - have no session (the common path) → render the "Check your
  //     inbox" screen. Clicking the confirm link in the email lands them
  //     at /?page=dashboard with a real session.
  // ── X-press behavior ───────────────────────────────────────────────────
  // If the student has at least entered their email, offer to send a
  // magic-link so they can come back from any device. Otherwise just
  // close (localStorage still holds their step 1-2 data for this device).
  const handleClose = () => {
    if (saving) return;
    if (isValidEmail(form.email) && step >= 3 && !signedInUser) {
      setShowSaveModal(true);
    } else {
      onBack();
    }
  };

  // Save-and-exit: signInWithOtp with the wizard's current snapshot. The
  // trigger creates the profile + student_info rows server-side; the
  // student gets a magic-link email; on click they land signed-in on the
  // dashboard with the "Finish your application" banner.
  const saveAndExit = async () => {
    if (captcha.required) { setSaveError('Please complete the captcha first.'); return; }
    setSaving(true); setSaveError('');
    try {
      const emailLower = form.email.trim().toLowerCase();
      const { error: otpErr } = await window.supa.auth.signInWithOtp({
        email: emailLower,
        options: {
          ...(captchaToken ? { captchaToken } : {}),
          shouldCreateUser: true,
          emailRedirectTo: location.origin + '/?page=dashboard',
          data: {
            mastery_role:       'student',
            full_name:          form.name.trim() || (form.email.split('@')[0] || ''),
            phone:              canonicalPhone,
            topics_of_interest: form.topic ? [form.topic] : [],
            availability:       form.availability,
            availability_note:  form.availabilityNote || '',
            timezone:           form.timezone,
          },
        },
      });
      try { captcha.reset && captcha.reset(); } catch (_) {}
      if (otpErr) {
        setSaveError(otpErr.message || "Couldn't send the link. Try again in a minute.");
        return;
      }
      // The signup-timezone-fix listener applies tz to profiles on first
      // SIGNED_IN — stash it so that path fires even though we're calling
      // signInWithOtp, not signUp (the wrapper covers signUp only).
      try { window.localStorage.setItem('mastery_signup_tz:' + emailLower, form.timezone); } catch (_) {}
      setShowSaveModal(false);
      setPendingConfirmEmail(emailLower);
    } finally {
      setSaving(false);
    }
  };

  const finish = async () => {
    if (captcha.required) { setError('Please complete the captcha.'); return; }
    setBusy(true); setError('');
    // Reset captcha after the submit so a retry (e.g. validation error)
    // doesn't reuse the consumed token — hCaptcha returns
    // "already-seen-response" if the same token is verified twice.
    const _resetAfter = () => { try { captcha.reset && captcha.reset(); } catch (_) {} };

    // ── Signed-in completion path ───────────────────────────────────────
    // The student came back via the magic link from a previous save-and-
    // exit. Their auth.users row + profile + student_info already exist
    // (created by the trigger from the signInWithOtp metadata). Update
    // those rows directly instead of calling supa.auth.signUp.
    if (signedInUser) {
      try {
        await window.supa.from('profiles').update({
          full_name: form.name.trim(),
          timezone:  form.timezone,
        }).eq('id', signedInUser.id);
        // upsert in case student_info row never got created (defensive —
        // the trigger should have created it on signInWithOtp, but a v33
        // signup pre-fix may not have).
        await window.supa.from('student_info').upsert({
          user_id:            signedInUser.id,
          phone:              canonicalPhone,
          topics_of_interest: form.topic ? [form.topic] : [],
          availability:       form.availability,
          availability_note:  form.availabilityNote || '',
        }, { onConflict: 'user_id' });
        clearSavedProgress();
        if (window.AUTH?.reloadProfile) await window.AUTH.reloadProfile();
        await window.DASHBOARD_DATA.refresh();
        onComplete();
        return;
      } catch (uErr) {
        setError(uErr.message || 'Could not save your application.');
        return;
      } finally {
        setBusy(false);
      }
    }

    try {
      const email    = form.email.trim().toLowerCase();
      const password = newRandomPassword();
      persistPhoneCred(canonicalPhone, password);

      const { data, error: signUpErr } = await window.supa.auth.signUp({
        email, password,
        options: {
          ...(captchaToken ? { captchaToken } : {}),
          // Where Supabase sends the user back to after they click the
          // confirm-email link. ?page=dashboard is recognised by app.jsx
          // and routes straight to the student portal.
          emailRedirectTo: location.origin + '/?page=dashboard',
          data: {
            mastery_role: 'student',
            full_name:    form.name.trim(),
            phone:        canonicalPhone,
            topics_of_interest: form.topic ? [form.topic] : [],
            availability:       form.availability,
            availability_note:  form.availabilityNote || '',
            // The v33 trigger doesn't read this column yet, but we ship
            // it so a future trigger update can land profiles.timezone
            // server-side. The client-side update below covers prod today.
            timezone:           form.timezone,
          },
        },
      });
      _resetAfter();  // token consumed by Supabase, force fresh challenge next try
      if (signUpErr) {
        if (/registered|exists/i.test(signUpErr.message || '')) {
          throw new Error('That email already has an account. Go back and use "Log in" with the magic-link option.');
        }
        throw signUpErr;
      }

      // Supabase obfuscates duplicate signups to prevent email-enumeration.
      // This project runs with mailer_autoconfirm=false (email confirmation
      // required), so the duplicate signal is data.user === null with no
      // error. The identities=[] heuristic only applies under
      // mailer_autoconfirm=true and would false-positive here, because
      // fresh unconfirmed users also come back with identities=[].
      if (!data?.user) {
        throw new Error('That email already has an account. Go back and use "Log in" with the magic-link option.');
      }

      if (!data.session) {
        // The trigger has already created the profile + student_info rows.
        // We just need the user to confirm their email. Render the
        // "Check your inbox" screen. The saved-progress entry is no
        // longer useful — clear it so a re-visit lands on a clean wizard.
        clearSavedProgress();
        setPendingConfirmEmail(email);
        return;
      }

      // Session present (auto-confirm path) — update profiles.timezone
      // to the zone the student picked on step 5 (the v33 trigger
      // doesn't read raw_user_meta_data->>timezone, so it defaulted to
      // 'America/New_York'). RLS allows the user to update their own
      // row now that the session is live. Non-fatal if it fails — they
      // can re-pick later from the dashboard.
      try {
        await window.supa.from('profiles')
          .update({ timezone: form.timezone })
          .eq('id', data.user.id);
      } catch (tzErr) {
        console.warn('[onboarding] profiles.timezone update failed', tzErr);
      }

      // Auto-confirm path completed — wizard's state is now persisted
      // server-side, so drop the localStorage cache.
      clearSavedProgress();
      // Reload AUTH to pick up the profile row the trigger just inserted,
      // then navigate. Without reloadProfile the dashboard hangs on
      // "Loading…" until refresh because the SIGNED_IN race leaves
      // AUTH.profile = null.
      if (window.AUTH?.reloadProfile) await window.AUTH.reloadProfile();
      await window.DASHBOARD_DATA.refresh();
      onComplete();
    } catch (e) {
      setError(e.message || 'Something went wrong. Try again.');
    } finally {
      setBusy(false);
    }
  };

  // ── Post-signup: waiting for email confirmation ──────────────────
  // signUp succeeded but Supabase didn't return a session. The trigger
  // has already created the profile row from raw_user_meta_data, so the
  // student lands straight on /dashboard the moment they click the link.
  if (pendingConfirmEmail) {
    return (
      <div style={{ minHeight:'100vh', background:'oklch(98.5% 0.007 60)', display:'flex', alignItems:'center', justifyContent:'center', fontFamily:"'Plus Jakarta Sans', sans-serif", padding:'32px 24px', position:'relative' }}>
        <button onClick={onBack} aria-label="Close" title="Back to site"
          style={{ position:'absolute', top:18, right:18, width:38, height:38, borderRadius:'50%', background:'oklch(95% 0.005 60)', border:'1px solid oklch(90% 0.01 265)', cursor:'pointer', fontSize:18, lineHeight:1, color:'oklch(40% 0.04 265)', fontFamily:"'Plus Jakarta Sans', sans-serif", display:'flex', alignItems:'center', justifyContent:'center', zIndex:10 }}>
          ×
        </button>
        <div style={{ width:'100%', maxWidth:460, textAlign:'center' }}>
          <div style={{ fontFamily:"'Cormorant Garamond', serif", fontSize:22, fontWeight:700, letterSpacing:'0.12em', color:'oklch(22% 0.06 265)', marginBottom:32 }}>COACHING</div>
          <div style={{ fontFamily:"'Cormorant Garamond', serif", fontSize:30, fontWeight:600, color:'oklch(18% 0.03 265)', marginBottom:16, lineHeight:1.2 }}>Check your inbox</div>
          <div style={{ fontSize:16, color:'oklch(40% 0.04 265)', marginBottom:14, lineHeight:1.7 }}>
            We sent a link to <strong style={{ color:'oklch(18% 0.03 265)' }}>{pendingConfirmEmail}</strong>.
          </div>
          <div style={{ fontSize:15, color:'oklch(56% 0.03 265)', marginBottom:28, lineHeight:1.7 }}>
            Click the link in that email to continue learning — we'll send you straight to your dashboard so you can finish setting up your account.
          </div>
          <div style={{ fontSize:13, color:'oklch(62% 0.03 265)', lineHeight:1.6 }}>
            Didn't get it? Check your spam folder, or use the "Log in" link from the homepage with the same email to resend.
          </div>
        </div>
      </div>
    );
  }

  return (
    <div style={{ minHeight:'100vh', background:'oklch(98.5% 0.007 60)', display:'flex', alignItems:'center', justifyContent:'center', fontFamily:"'Plus Jakarta Sans', sans-serif", padding:'32px 24px', position:'relative' }}>
      {/* X close — routes to save-and-exit modal when an email is present
          (step 3+ on the public flow); otherwise just exits to home. */}
      <button onClick={handleClose} aria-label="Close" title="Back to site"
        style={{ position:'absolute', top:18, right:18, width:38, height:38, borderRadius:'50%', background:'oklch(95% 0.005 60)', border:'1px solid oklch(90% 0.01 265)', cursor:'pointer', fontSize:18, lineHeight:1, color:'oklch(40% 0.04 265)', fontFamily:"'Plus Jakarta Sans', sans-serif", display:'flex', alignItems:'center', justifyContent:'center', zIndex:10 }}>
        ×
      </button>

      {showSaveModal && (
        <div onClick={() => !saving && setShowSaveModal(false)}
          style={{ position:'fixed', inset:0, background:'rgba(10,14,30,0.42)', display:'flex', alignItems:'center', justifyContent:'center', zIndex:50, padding:'20px' }}>
          <div onClick={e => e.stopPropagation()}
            style={{ background:'#fff', borderRadius:18, padding:'26px 22px', maxWidth:420, width:'100%', fontFamily:"'Plus Jakarta Sans', sans-serif", boxShadow:'0 24px 60px rgba(0,0,0,0.22)' }}>
            <div style={{ fontFamily:"'Cormorant Garamond', serif", fontSize:24, fontWeight:600, color:'oklch(18% 0.03 265)', marginBottom:8, lineHeight:1.2 }}>Save your progress?</div>
            <div style={{ fontSize:15, color:'oklch(46% 0.04 265)', lineHeight:1.65, marginBottom:14 }}>
              We'll email a link to <strong style={{ color:'oklch(22% 0.06 265)' }}>{form.email.trim().toLowerCase()}</strong> so you can pick up where you left off and finish setting up your account.
            </div>
            {captcha.widget}
            {saveError && (
              <div style={{ fontSize:13, color:'oklch(38% 0.1 25)', marginTop:10, background:'oklch(96% 0.06 25)', padding:'9px 12px', borderRadius:9, lineHeight:1.5 }}>
                {saveError}
              </div>
            )}
            <div style={{ display:'flex', flexDirection:'column', gap:10, marginTop:16 }}>
              <button onClick={saveAndExit} disabled={saving || captcha.required}
                style={{ padding:'15px 18px', borderRadius:12, border:'none',
                  background:(saving||captcha.required)?'oklch(88% 0.01 265)':'oklch(22% 0.06 265)',
                  color:(saving||captcha.required)?'oklch(60% 0.02 265)':'#fff',
                  fontSize:15, fontWeight:700, cursor:(saving||captcha.required)?'default':'pointer',
                  fontFamily:"'Plus Jakarta Sans', sans-serif" }}>
                {saving ? 'Sending…' : 'Email me a link →'}
              </button>
              <button onClick={() => { setShowSaveModal(false); onBack(); }} disabled={saving}
                style={{ padding:'12px', borderRadius:12, border:'none', background:'none', color:'oklch(54% 0.03 265)', fontSize:13.5, cursor:saving?'default':'pointer', fontFamily:"'Plus Jakarta Sans', sans-serif", textDecoration:'underline' }}>
                Just exit — my progress is saved on this device
              </button>
            </div>
          </div>
        </div>
      )}
      <div style={{ width:'100%', maxWidth:500 }}>
        <div style={{ fontFamily:"'Cormorant Garamond', serif", fontSize:22, fontWeight:700, letterSpacing:'0.12em', color:'oklch(22% 0.06 265)', marginBottom:48, textAlign:'center' }}>COACHING</div>

        <div style={{ display:'flex', gap:8, justifyContent:'center', marginBottom:40 }}>
          {steps.map((_,i) => <div key={i} style={{ width:i+1===step?28:8, height:8, borderRadius:4, background:i+1<=step?'oklch(22% 0.06 265)':'oklch(88% 0.02 265)', transition:'all 0.25s' }} />)}
        </div>

        <div style={{ fontSize:13, color:'oklch(62% 0.03 265)', marginBottom:8 }}>Step {step} of {steps.length}</div>
        <div style={{ fontFamily:"'Cormorant Garamond', serif", fontSize:30, fontWeight:600, color:'oklch(18% 0.03 265)', marginBottom:6, lineHeight:1.2 }}>{cur.q}</div>
        <div style={{ fontSize:15, color:'oklch(58% 0.03 265)', marginBottom:26, lineHeight:1.6 }}>{cur.hint}</div>

        {cur.body}

        {isLastStep && captcha.widget}

        {error && <div style={{ fontSize:14, color:'oklch(38% 0.1 25)', marginTop:14, background:'oklch(96% 0.06 25)', padding:'10px 14px', borderRadius:10 }}>{error}</div>}

        <div style={{ display:'flex', gap:12, marginTop:24 }}>
          {step > 1 && (
            <button onClick={() => setStep(s => s-1)} disabled={busy} style={{ padding:'17px 28px', borderRadius:14, border:'2.5px solid oklch(86% 0.02 265)', background:'#fff', fontSize:16, fontWeight:600, cursor:'pointer', color:'oklch(44% 0.04 265)', fontFamily:"'Plus Jakarta Sans', sans-serif" }}>Back</button>
          )}
          <button
            onClick={() => { if(step < steps.length) setStep(s => s+1); else finish(); }}
            disabled={!cur.ok || busy || (isLastStep && captcha.required)}
            style={{ flex:1, padding:'17px 0', borderRadius:14, border:'none', background:(cur.ok && !busy)?'oklch(22% 0.06 265)':'oklch(88% 0.01 265)', color:(cur.ok && !busy)?'#fff':'oklch(65% 0.02 265)', fontSize:18, fontWeight:700, cursor:(cur.ok && !busy)?'pointer':'default', fontFamily:"'Plus Jakarta Sans', sans-serif", transition:'background 0.15s' }}>
            {busy ? 'Saving…' : (step < steps.length ? 'Continue →' : 'Get started →')}
          </button>
        </div>

        {step === 1 && (
          <div style={{ textAlign:'center', marginTop:24 }}>
            <button onClick={onBack} style={{ background:'none', border:'none', fontSize:15, color:'oklch(60% 0.03 265)', cursor:'pointer', fontFamily:"'Plus Jakarta Sans', sans-serif" }}>← Back to site</button>
          </div>
        )}
      </div>
    </div>
  );
};

// ── Login ───────────────────────────────────────────────────────────
// Primary login = magic link (passwordless). The phone-based legacy
// path stays available behind a "use phone instead" toggle.

window.StudentOnboarding = StudentOnboarding;
