// src/features/instructor/ui/become-instructor-v2-steps.jsx
//
// The seven wizard step components + their two small row helpers,
// extracted from become-instructor-v2.jsx for size.
//
// Public on window:
//   BIV2Subjects, BIV2HowItWorks, BIV2BasicInfo, BIV2Profile,
//   BIV2Agreement, BIV2Review, BIV2Confirmation, ReviewRow, BIV2ConfRow.
//
// Depends on (via window): BIV2Field, BIV2NavBar (small layout
// helpers in become-instructor-v2.jsx) and the shared style consts
// biv2H1/biv2H3/biv2Sub/biv2Input/biv2ErrorBox/biv2CheckLabel.

const {
  BIV2Field, BIV2NavBar,
  biv2H1, biv2H3, biv2Sub, biv2Input, biv2ErrorBox, biv2CheckLabel,
} = window;

// Picks an emoji per category by name. The previous version showed a
// chevron only — Joe asked for category-themed icons. Keyed on the lower-
// cased name so admin can rename categories without rebuilding this map.
const CATEGORY_EMOJI = {
  music: '🎵',
  'visual arts': '🎨',
  dance: '💃',
  'sports & fitness': '🏆',
  'theater & performance': '🎭',
  languages: '🗣️',
  language: '🗣️',
  academic: '📚',
  academics: '📚',
  technology: '💻',
  tech: '💻',
};
const catEmoji = (name) => CATEGORY_EMOJI[(name || '').toLowerCase()] || '✨';

const BIV2Subjects = ({ categories, progress, config, advance, setStep, isMobile }) => {
  const [selected, setSelected] = React.useState(new Set(progress?.subjects_picked || []));
  const [openCat, setOpenCat] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const copy = config['step.subjects'] || {};

  const toggle = (id) => {
    setSelected(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else {
        if (next.size >= 5) return prev;
        next.add(id);
      }
      return next;
    });
  };

  const onNext = async () => {
    setError(null);
    if (selected.size === 0) { setError('Pick at least one subject.'); return; }
    setBusy(true);
    try {
      const { error: e } = await window.supa.rpc('signup_save_subjects', { p_subject_ids: Array.from(selected) });
      if (e) throw e;
      await advance();
      setStep(2);
    } catch (e) {
      setError(window.friendlyError ? window.friendlyError(e, 'Could not save') : e.message);
    } finally { setBusy(false); }
  };

  const selectedNames = categories.flatMap(c => c.subjects.filter(s => selected.has(s.id)).map(s => s.name));

  return (
    <div>
      <h1 style={biv2H1}>{copy.title || 'Choose your subjects'}</h1>
      <p style={biv2Sub}>{copy.subtitle}</p>
      <div style={{ background:'#fff', border:'1px solid oklch(94% 0.01 265)', borderRadius:12, padding: isMobile ? 16 : 28, marginTop:18 }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:14 }}>
          <strong style={{ fontSize:14 }}>{selected.size === 0 ? 'None selected' : `${selected.size} of 5 selected`}</strong>
          {selectedNames.length > 0 && (
            <span style={{ fontSize:12, color:'oklch(50% 0.03 265)' }}>{selectedNames.join(', ')}</span>
          )}
        </div>
        <div style={{ display:'grid', gap:8 }}>
          {categories.map(c => (
            <div key={c.id} style={{ border:'1px solid oklch(92% 0.01 265)', borderRadius:8, overflow:'hidden' }}>
              <button type="button" onClick={() => setOpenCat(o => o === c.id ? null : c.id)}
                style={{ width:'100%', textAlign:'left',
                  padding: isMobile ? '14px 16px' : '12px 16px',
                  background: openCat === c.id ? 'oklch(96% 0.01 265)' : '#fff',
                  border:'none', cursor:'pointer',
                  display:'flex',
                  flexDirection: isMobile ? 'column' : 'row',
                  justifyContent: isMobile ? 'flex-start' : 'space-between',
                  alignItems: isMobile ? 'flex-start' : 'center',
                  gap: isMobile ? 3 : 14,
                  fontFamily:"'Plus Jakarta Sans', sans-serif", fontSize:14, fontWeight:600 }}>
                <span style={{ color:'oklch(28% 0.06 265)', whiteSpace:'nowrap', display:'inline-flex', alignItems:'center', gap:8 }}>
                  <span style={{ fontSize:18, lineHeight:1 }}>{catEmoji(c.name)}</span>
                  <span style={{ color:'oklch(50% 0.04 265)', fontSize:12 }}>{openCat === c.id ? '▼' : '▶'}</span>
                  <span>{c.name}</span>
                </span>
                <span style={{ fontSize:12, color:'oklch(50% 0.03 265)', fontWeight:500, textAlign: isMobile ? 'left' : 'right' }}>{c.description}</span>
              </button>
              {openCat === c.id && (
                <div style={{ padding:'10px 16px 14px', display:'flex', flexWrap:'wrap', gap:8, borderTop:'1px solid oklch(94% 0.01 265)', background:'oklch(99% 0.005 265)' }}>
                  {c.subjects.map(s => {
                    const on = selected.has(s.id);
                    const disabled = !on && selected.size >= 5;
                    return (
                      <button key={s.id} type="button" onClick={() => toggle(s.id)} disabled={disabled}
                        style={{ padding:'7px 13px', borderRadius:16, fontSize:13, fontWeight:600,
                          cursor: disabled ? 'not-allowed' : 'pointer', fontFamily:"'Plus Jakarta Sans', sans-serif",
                          background: on ? 'oklch(22% 0.06 265)' : '#fff',
                          color: on ? '#fff' : (disabled ? 'oklch(70% 0.02 265)' : 'oklch(40% 0.04 265)'),
                          border: on ? '1.5px solid oklch(22% 0.06 265)' : '1.5px solid oklch(88% 0.02 265)',
                          opacity: disabled ? 0.55 : 1 }}>
                        {on ? '✓ ' : ''}{s.name}
                      </button>
                    );
                  })}
                </div>
              )}
            </div>
          ))}
        </div>
      </div>
      {error && <div style={biv2ErrorBox}>{error}</div>}
      <BIV2NavBar onBack={null} onNext={onNext} nextLabel="Next →" busy={busy} />
    </div>
  );
};

// =====================================================================
// STEP 2 — How Coaching Works (4 explainer sub-screens + quiz).
// =====================================================================
const BIV2HowItWorks = ({ quiz, config, advance, setStep, isMobile }) => {
  // Sub-flow: 4 explainer pages, each followed by a quiz Q (if one matches step_key).
  const SUB = ['relationship', 'students', 'payment', 'rules'];
  const [sub, setSub] = React.useState(0);
  const [phase, setPhase] = React.useState('read');        // 'read' | 'quiz' | 'wrong'
  // Answers are mirrored in a ref because the LAST quiz answer is followed
  // by setTimeout(submitAll, 900). React state set inside the click handler
  // hasn't been flushed into the closure that submitAll sees, so without
  // the ref the RPC gets N-1 answers and rejects with "Quiz not complete".
  // Bug found 2026-05-21 by the A-to-Z signup test.
  const [answers, setAnswers] = React.useState({});
  const answersRef = React.useRef({});
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const copy = config['step.how_it_works'] || {};

  const subKey = SUB[sub];
  const subQuestion = quiz.find(q => q.step_key === subKey);

  const subTitleKey = `${subKey}_title`;
  const subBodyKey  = `${subKey}_body`;

  const onReadNext = () => {
    if (subQuestion) setPhase('quiz');
    else moveToNextSub();
  };

  const moveToNextSub = () => {
    if (sub < SUB.length - 1) {
      setSub(sub + 1); setPhase('read');
    } else {
      // All explainers done — finalize.
      submitAll();
    }
  };

  const submitAll = async () => {
    setBusy(true); setError(null);
    try {
      const { error: e } = await window.supa.rpc('signup_complete_how_it_works', { p_answers: answersRef.current });
      if (e) throw e;
      await advance();
      setStep(3);
    } catch (e) {
      setError(window.friendlyError ? window.friendlyError(e, 'Could not save') : e.message);
      // Reset to first wrong question if validation failed.
      setSub(0); setPhase('read');
    } finally { setBusy(false); }
  };

  const submitQuizAnswer = async (idx) => {
    if (!subQuestion) return;
    setBusy(true); setError(null);
    try {
      const { data: correct, error: e } = await window.supa.rpc('submit_quiz_answer', {
        p_question_id: subQuestion.id, p_selected_index: idx,
      });
      if (e) throw e;
      if (correct) {
        answersRef.current = { ...answersRef.current, [subQuestion.id]: idx };
        setAnswers(answersRef.current);
        // Brief "Correct!" flash then advance.
        setPhase('correct');
        setTimeout(() => moveToNextSub(), 900);
      } else {
        setPhase('wrong');
      }
    } catch (e) {
      setError(window.friendlyError ? window.friendlyError(e, 'Quiz error') : e.message);
    } finally { setBusy(false); }
  };

  return (
    <div>
      <h1 style={biv2H1}>{copy[subTitleKey] || copy.title}</h1>
      <p style={biv2Sub}>{copy[subBodyKey] || copy.subtitle}</p>
      {phase === 'read' && (
        <div style={{ marginTop:24 }}>
          <BIV2NavBar
            onBack={sub > 0 ? () => { setSub(sub-1); setPhase('read'); } : () => setStep(1)}
            onNext={onReadNext} nextLabel="Next →" busy={busy}
          />
        </div>
      )}
      {(phase === 'quiz' || phase === 'wrong' || phase === 'correct') && subQuestion && (
        <div style={{ background:'#fff', border:'1px solid oklch(94% 0.01 265)', borderRadius:12, padding: isMobile ? 18 : 28, marginTop:24 }}>
          <div style={{ fontSize:12, color:'oklch(50% 0.03 265)', marginBottom:4 }}>Question {sub + 1}: {subKey.charAt(0).toUpperCase() + subKey.slice(1)}</div>
          <h3 style={{ fontFamily:"'Plus Jakarta Sans', sans-serif", fontSize:18, fontWeight:700, color:'oklch(22% 0.06 265)', marginBottom:18 }}>{subQuestion.question}</h3>
          {phase === 'wrong' && (
            <div style={{ background:'oklch(96% 0.04 80)', border:'1px solid oklch(85% 0.1 80)', borderRadius:8, padding:'10px 14px', fontSize:13, color:'oklch(35% 0.13 80)', marginBottom:14 }}>
              Whoops, please review this information and try that question again.
            </div>
          )}
          {phase === 'correct' && (
            <div style={{ background:'oklch(95% 0.07 155)', border:'1px solid oklch(80% 0.13 155)', borderRadius:8, padding:'10px 14px', fontSize:13, color:'oklch(32% 0.13 155)', marginBottom:14 }}>
              ✓ Correct!
            </div>
          )}
          <div style={{ display:'grid', gap:10 }}>
            {(subQuestion.options || []).map((opt, i) => (
              <button key={i} type="button" disabled={busy || phase === 'correct'}
                onClick={() => submitQuizAnswer(i)}
                style={{ textAlign:'left', padding:'12px 16px', borderRadius:8, border:'1.5px solid oklch(88% 0.02 265)', background:'#fff', cursor: busy ? 'wait' : 'pointer', fontSize:14, color:'oklch(28% 0.04 265)', fontFamily:"'Plus Jakarta Sans', sans-serif", lineHeight:1.5 }}>
                ○ &nbsp;{opt}
              </button>
            ))}
          </div>
          {error && <div style={biv2ErrorBox}>{error}</div>}
          {phase === 'wrong' && (
            <div style={{ marginTop:18, display:'flex', justifyContent:'center' }}>
              <button type="button" onClick={() => setPhase('read')} style={{
                background:'oklch(22% 0.06 265)', color:'#fff', border:'none', borderRadius:999,
                padding: isMobile ? '14px 32px' : '14px 40px',
                fontWeight:700, fontSize:15, cursor:'pointer',
                fontFamily:"'Plus Jakarta Sans', sans-serif",
                boxShadow:'0 4px 14px rgba(0,0,0,0.08)',
                minWidth: isMobile ? 180 : 220,
              }}>↻ Try again</button>
            </div>
          )}
        </div>
      )}
      {/* If no quiz question matches this sub-step, the navbar already handles next. */}
    </div>
  );
};

// =====================================================================
// STEP 3 — Basic information.
//
// Form layout reworked 2026-05-21 (overhaul #2) per Joe:
//   - Gender, time-zone select, and "privacy radius" deleted entirely.
//     Time-zone is now silently auto-detected via Intl.
//   - Rate and phone are always required (* indicator); fallback list
//     in code, not from signup_config, so the wizard isn't a no-op if
//     admin hasn't pre-configured the required list.
//   - Address fields and travel-radius are required + visible only when
//     the tutor checks "I teach in person". Online-only tutors don't
//     need to dox themselves.
//   - Phone uses an inline country selector with per-country digit
//     formatting (~25 hand-maintained countries; bare digit fallback
//     for the rest — no npm dep, CDN+JSX constraint).
//   - "I am not formally educated" toggle collapses the education
//     section so non-traditional teachers can submit without entering
//     a fake college.
//   - On Save & continue, the first invalid field is scrolled into
//     view + focused. The asterisk on the label tells the user up-front
//     which fields are required, so they don't have to scroll back from
//     the error banner to find what's missing.
// =====================================================================

// ~25 common countries with dial codes + a per-country digit-grouping
// mask. mask() takes raw digits and returns a display string.
const PHONE_COUNTRIES = [
  { iso:'US', flag:'🇺🇸', name:'United States',     dial:'+1',   max:10, mask: d => d.length<=3 ? d : d.length<=6 ? `(${d.slice(0,3)}) ${d.slice(3)}` : `(${d.slice(0,3)}) ${d.slice(3,6)}-${d.slice(6)}` },
  { iso:'CA', flag:'🇨🇦', name:'Canada',            dial:'+1',   max:10, mask: d => d.length<=3 ? d : d.length<=6 ? `(${d.slice(0,3)}) ${d.slice(3)}` : `(${d.slice(0,3)}) ${d.slice(3,6)}-${d.slice(6)}` },
  { iso:'GB', flag:'🇬🇧', name:'United Kingdom',    dial:'+44',  max:10, mask: d => d.length<=4 ? d : d.length<=7 ? `${d.slice(0,4)} ${d.slice(4)}` : `${d.slice(0,4)} ${d.slice(4,7)} ${d.slice(7)}` },
  { iso:'IE', flag:'🇮🇪', name:'Ireland',           dial:'+353', max:9,  mask: d => d.length<=2 ? d : d.length<=5 ? `${d.slice(0,2)} ${d.slice(2)}` : `${d.slice(0,2)} ${d.slice(2,5)} ${d.slice(5)}` },
  { iso:'AU', flag:'🇦🇺', name:'Australia',         dial:'+61',  max:9,  mask: d => d.length<=3 ? d : d.length<=6 ? `${d.slice(0,3)} ${d.slice(3)}` : `${d.slice(0,3)} ${d.slice(3,6)} ${d.slice(6)}` },
  { iso:'NZ', flag:'🇳🇿', name:'New Zealand',       dial:'+64',  max:9,  mask: d => d.length<=2 ? d : d.length<=5 ? `${d.slice(0,2)} ${d.slice(2)}` : `${d.slice(0,2)} ${d.slice(2,5)} ${d.slice(5)}` },
  { iso:'IN', flag:'🇮🇳', name:'India',             dial:'+91',  max:10, mask: d => d.length<=5 ? d : `${d.slice(0,5)} ${d.slice(5)}` },
  { iso:'MX', flag:'🇲🇽', name:'Mexico',            dial:'+52',  max:10, mask: d => d.length<=2 ? d : d.length<=6 ? `${d.slice(0,2)} ${d.slice(2)}` : `${d.slice(0,2)} ${d.slice(2,6)} ${d.slice(6)}` },
  { iso:'BR', flag:'🇧🇷', name:'Brazil',            dial:'+55',  max:11, mask: d => d.length<=2 ? d : d.length<=7 ? `(${d.slice(0,2)}) ${d.slice(2)}` : `(${d.slice(0,2)}) ${d.slice(2,7)}-${d.slice(7)}` },
  { iso:'FR', flag:'🇫🇷', name:'France',            dial:'+33',  max:9,  mask: d => (d.match(/.{1,2}/g) || []).join(' ') },
  { iso:'DE', flag:'🇩🇪', name:'Germany',           dial:'+49',  max:11, mask: d => d.length<=3 ? d : `${d.slice(0,3)} ${d.slice(3)}` },
  { iso:'ES', flag:'🇪🇸', name:'Spain',             dial:'+34',  max:9,  mask: d => (d.match(/.{1,3}/g) || []).join(' ') },
  { iso:'IT', flag:'🇮🇹', name:'Italy',             dial:'+39',  max:10, mask: d => d.length<=3 ? d : `${d.slice(0,3)} ${d.slice(3)}` },
  { iso:'NL', flag:'🇳🇱', name:'Netherlands',       dial:'+31',  max:9,  mask: d => d.length<=2 ? d : `${d.slice(0,2)} ${d.slice(2)}` },
  { iso:'SE', flag:'🇸🇪', name:'Sweden',            dial:'+46',  max:9,  mask: d => d.length<=2 ? d : `${d.slice(0,2)} ${d.slice(2)}` },
  { iso:'CH', flag:'🇨🇭', name:'Switzerland',       dial:'+41',  max:9,  mask: d => d.length<=2 ? d : `${d.slice(0,2)} ${d.slice(2)}` },
  { iso:'PT', flag:'🇵🇹', name:'Portugal',          dial:'+351', max:9,  mask: d => (d.match(/.{1,3}/g) || []).join(' ') },
  { iso:'PL', flag:'🇵🇱', name:'Poland',            dial:'+48',  max:9,  mask: d => (d.match(/.{1,3}/g) || []).join(' ') },
  { iso:'JP', flag:'🇯🇵', name:'Japan',             dial:'+81',  max:10, mask: d => d.length<=2 ? d : d.length<=6 ? `${d.slice(0,2)}-${d.slice(2)}` : `${d.slice(0,2)}-${d.slice(2,6)}-${d.slice(6)}` },
  { iso:'KR', flag:'🇰🇷', name:'South Korea',       dial:'+82',  max:10, mask: d => d.length<=2 ? d : d.length<=6 ? `${d.slice(0,2)}-${d.slice(2)}` : `${d.slice(0,2)}-${d.slice(2,6)}-${d.slice(6)}` },
  { iso:'SG', flag:'🇸🇬', name:'Singapore',         dial:'+65',  max:8,  mask: d => d.length<=4 ? d : `${d.slice(0,4)} ${d.slice(4)}` },
  { iso:'HK', flag:'🇭🇰', name:'Hong Kong',         dial:'+852', max:8,  mask: d => d.length<=4 ? d : `${d.slice(0,4)} ${d.slice(4)}` },
  { iso:'AE', flag:'🇦🇪', name:'United Arab Em.',   dial:'+971', max:9,  mask: d => d.length<=2 ? d : `${d.slice(0,2)} ${d.slice(2)}` },
];
const phoneCountryByIso = (iso) => PHONE_COUNTRIES.find(c => c.iso === iso) || PHONE_COUNTRIES[0];
// Best-effort detect from the user's locale + tz. Falls back to US.
const detectPhoneCountry = () => {
  try {
    const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
    if (tz.startsWith('Europe/London')) return phoneCountryByIso('GB');
    if (tz.startsWith('Europe/Dublin')) return phoneCountryByIso('IE');
    if (tz.startsWith('Europe/Paris'))  return phoneCountryByIso('FR');
    if (tz.startsWith('Europe/Berlin')) return phoneCountryByIso('DE');
    if (tz.startsWith('Europe/Madrid')) return phoneCountryByIso('ES');
    if (tz.startsWith('Europe/Rome'))   return phoneCountryByIso('IT');
    if (tz.startsWith('Australia/'))    return phoneCountryByIso('AU');
    if (tz.startsWith('Pacific/Auckland')) return phoneCountryByIso('NZ');
    if (tz.startsWith('Asia/Tokyo'))    return phoneCountryByIso('JP');
    if (tz.startsWith('Asia/Seoul'))    return phoneCountryByIso('KR');
    if (tz.startsWith('Asia/Singapore'))return phoneCountryByIso('SG');
    if (tz.startsWith('Asia/Hong_Kong'))return phoneCountryByIso('HK');
    if (tz.startsWith('Asia/Kolkata'))  return phoneCountryByIso('IN');
    if (tz.startsWith('America/Toronto') || tz.startsWith('America/Vancouver') || tz.startsWith('America/Montreal')) return phoneCountryByIso('CA');
    if (tz.startsWith('America/Mexico')) return phoneCountryByIso('MX');
    if (tz.startsWith('America/Sao_Paulo') || tz.startsWith('America/Bahia')) return phoneCountryByIso('BR');
  } catch (_) {}
  return phoneCountryByIso('US');
};
// Strip a stored phone like "+1 (415) 555-1234" back into (iso, digits)
// so the field re-mounts in the same state on edit.
const parseStoredPhone = (stored) => {
  if (!stored) return { country: detectPhoneCountry(), digits: '' };
  const sorted = PHONE_COUNTRIES.slice().sort((a,b) => b.dial.length - a.dial.length);
  for (const c of sorted) {
    if (stored.startsWith(c.dial)) {
      const rest = stored.slice(c.dial.length).replace(/\D/g, '');
      return { country: c, digits: rest.slice(0, c.max) };
    }
  }
  return { country: detectPhoneCountry(), digits: stored.replace(/\D/g, '') };
};

const BIV2BasicInfo = ({ progress, config, instructorRow, advance, setStep, isMobile }) => {
  const copy = config['step.basic_info'] || {};

  // Auto-detected tz overrides whatever was stored; the field has no UI
  // any more, and detection is more accurate than the previous defaulting
  // to America/New_York. Falls back to NY if Intl is unavailable.
  const detectedTz = (() => {
    try {
      const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
      return tz && tz.length ? tz : 'America/New_York';
    } catch (_) { return 'America/New_York'; }
  })();

  const storedPhone = parseStoredPhone(instructorRow.phone || '');

  const [state, setState] = React.useState({
    time_zone: detectedTz,
    cancellation_notice: instructorRow.cancellation_notice || '',
    rate: instructorRow.rate || '',
    address_line1: instructorRow.address_line1 || '',
    address_line2: instructorRow.address_line2 || '',
    address_city: instructorRow.address_city || instructorRow.city || '',
    address_state: instructorRow.address_state || instructorRow.state || '',
    address_zip: instructorRow.address_zip || '',
    phone: instructorRow.phone || '',
    undergrad_college: instructorRow.undergrad_college || '',
    undergrad_major: instructorRow.undergrad_major || '',
    grad_college_1: instructorRow.grad_college_1 || '',
    grad_degree_1: instructorRow.grad_degree_1 || '',
    grad_college_2: instructorRow.grad_college_2 || '',
    grad_degree_2: instructorRow.grad_degree_2 || '',
    teaching_certification: instructorRow.teaching_certification || '',
    travel_radius_miles: instructorRow.travel_radius_miles || 20,
    teaches_online:    instructorRow.teaches_online    !== false,
    teaches_in_person: instructorRow.teaches_in_person === true,
  });
  const [phoneCountry, setPhoneCountry] = React.useState(storedPhone.country);
  const [phoneDigits, setPhoneDigits]   = React.useState(storedPhone.digits);
  const [noEducation, setNoEducation]   = React.useState(
    !instructorRow.undergrad_college && !instructorRow.grad_college_1 && !instructorRow.teaching_certification
  );
  const upd = (k) => (e) => setState(s => ({ ...s, [k]: e.target.value }));
  const updBool = (k) => (e) => setState(s => ({ ...s, [k]: e.target.checked }));
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [rateStats, setRateStats] = React.useState(null);

  const composedPhone = phoneDigits ? `${phoneCountry.dial} ${phoneCountry.mask(phoneDigits)}` : '';

  // Average rate hint. Top-10% number dropped per Joe — too aspirational
  // and makes the tutor's actual entry feel low.
  React.useEffect(() => {
    let alive = true;
    (async () => {
      const picked = progress?.subjects_picked || [];
      const { data, error: e } = await window.supa.rpc('signup_topic_rate_stats', { p_subject_ids: picked });
      if (!alive) return;
      if (!e && Array.isArray(data) && data[0]) setRateStats(data[0]);
    })();
    return () => { alive = false; };
  }, [progress?.subjects_picked]);

  // Scroll the first invalid field into view + focus its first input.
  const scrollToField = (id) => {
    const el = document.querySelector(`[data-field-id="${id}"]`);
    if (!el) return;
    el.scrollIntoView({ behavior:'smooth', block:'center' });
    const input = el.querySelector('input, select, textarea, button');
    if (input) setTimeout(() => input.focus(), 350);
  };

  const onNext = async () => {
    setError(null);
    // Default required list — hardcoded fallback so we don't depend on
    // admin having populated signup_config. teaches_in_person triggers
    // address requirement.
    const reqList = [];
    reqList.push({ id:'rate',  check: () => state.rate && Number(state.rate) >= 10, msg:'Please set an hourly rate of $10 or more.' });
    reqList.push({ id:'modality', check: () => state.teaches_online || state.teaches_in_person, msg:'Pick at least one teaching mode (online and/or in person).' });
    reqList.push({ id:'phone', check: () => phoneDigits && phoneDigits.length >= 5, msg:'A valid phone number is required.' });
    if (state.teaches_in_person) {
      reqList.push({ id:'address_line1', check: () => state.address_line1.trim(), msg:'Street address is required when you teach in person.' });
      reqList.push({ id:'address_city',  check: () => state.address_city.trim(),  msg:'City is required when you teach in person.' });
      reqList.push({ id:'address_state', check: () => state.address_state.trim(), msg:'State is required when you teach in person.' });
      reqList.push({ id:'address_zip',   check: () => state.address_zip.trim(),   msg:'ZIP code is required when you teach in person.' });
    }
    for (const r of reqList) {
      if (!r.check()) {
        setError(r.msg);
        scrollToField(r.id);
        return;
      }
    }
    setBusy(true);
    try {
      // Compose the final phone string in international format. Storage
      // format = "+1 (415) 555-1234". parseStoredPhone above re-hydrates it.
      const payload = { ...state, phone: composedPhone, time_zone: detectedTz };
      if (noEducation) {
        // Wipe the education fields so a re-submit doesn't leak stale
        // values from a previous (formal-ed) attempt.
        payload.undergrad_college = ''; payload.undergrad_major = '';
        payload.grad_college_1 = ''; payload.grad_degree_1 = '';
        payload.grad_college_2 = ''; payload.grad_degree_2 = '';
        payload.teaching_certification = '';
      }
      const { error: e } = await window.supa.rpc('signup_save_basic_info', { p: payload });
      if (e) throw e;
      await advance();
      setStep(4);
    } catch (e) {
      setError(window.friendlyError ? window.friendlyError(e, 'Could not save') : e.message);
    } finally { setBusy(false); }
  };

  const cardStyle = { background:'#fff', border:'1px solid oklch(94% 0.01 265)', borderRadius:12, padding: isMobile ? 18 : 24 };

  return (
    <div>
      <h1 style={biv2H1}>{copy.title || 'Basic information'}</h1>
      <p style={biv2Sub}>{copy.subtitle}</p>
      <p style={{ fontSize:12, color:'oklch(55% 0.03 265)', marginTop:6 }}>Fields marked <span style={{ color:'oklch(55% 0.20 25)', fontWeight:700 }}>*</span> are required.</p>
      <div style={{ display:'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 18 : 32, marginTop:18 }}>
        <div style={cardStyle}>
          <h3 style={biv2H3}>Your teaching</h3>
          <BIV2Field label="Hourly rate (USD)" hint={copy.hourly_rate_hint} required fieldId="rate">
            <div style={{ display:'flex', alignItems:'center', gap:6 }}>
              <span style={{ fontSize:18, color:'oklch(45% 0.04 265)' }}>$</span>
              <input type="number" min={10} max={500} value={state.rate} onChange={upd('rate')} style={{...biv2Input, maxWidth:140}} required />
              <span style={{ fontSize:13, color:'oklch(50% 0.03 265)', marginLeft:4 }}>/hr</span>
            </div>
            {rateStats && rateStats.sample_size > 0 && (
              <div style={{ marginTop:8, padding:'8px 12px', background:'oklch(96% 0.02 90)', border:'1px solid oklch(88% 0.04 90)', borderRadius:8, fontSize:12.5, color:'oklch(35% 0.07 90)' }}>
                Average for your subjects: <strong>${rateStats.avg_rate}/hr</strong>
              </div>
            )}
          </BIV2Field>

          <BIV2Field label="How do you teach?" hint="Pick at least one. You can change this later." required fieldId="modality">
            <label style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 0', cursor:'pointer', fontSize:14, color:'oklch(28% 0.05 265)' }}>
              <input type="checkbox" checked={state.teaches_online} onChange={updBool('teaches_online')} style={{ width:18, height:18 }} />
              <span>💻 I teach online (video lessons)</span>
            </label>
            <label style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 0', cursor:'pointer', fontSize:14, color:'oklch(28% 0.05 265)' }}>
              <input type="checkbox" checked={state.teaches_in_person} onChange={updBool('teaches_in_person')} style={{ width:18, height:18 }} />
              <span>🏠 I teach in person</span>
            </label>
          </BIV2Field>

          <BIV2Field label="Cancellation notice" fieldId="cancellation_notice">
            <select value={state.cancellation_notice} onChange={upd('cancellation_notice')} style={biv2Input}>
              <option value="">None</option>
              <option value="2h">2 hours</option>
              <option value="6h">6 hours</option>
              <option value="12h">12 hours</option>
              <option value="24h">24 hours</option>
            </select>
          </BIV2Field>

          <BIV2Field label="Phone number" hint="We'll only call about lesson scheduling or emergencies." required fieldId="phone">
            <div style={{ display:'flex', gap:8 }}>
              <select value={phoneCountry.iso} onChange={(e) => {
                  const next = phoneCountryByIso(e.target.value);
                  setPhoneCountry(next);
                  setPhoneDigits(d => d.slice(0, next.max));
                }} style={{ ...biv2Input, maxWidth: 130, flex:'0 0 130px' }}>
                {PHONE_COUNTRIES.map(c => (
                  <option key={c.iso} value={c.iso}>{c.flag} {c.dial}</option>
                ))}
              </select>
              <input type="tel" inputMode="tel"
                value={phoneCountry.mask(phoneDigits)}
                onChange={e => {
                  const onlyDigits = e.target.value.replace(/\D/g, '').slice(0, phoneCountry.max);
                  setPhoneDigits(onlyDigits);
                }}
                placeholder={phoneCountry.mask('5'.repeat(phoneCountry.max))}
                style={{ ...biv2Input, flex:1 }} />
            </div>
          </BIV2Field>

          {state.teaches_in_person && (
            <>
              <h3 style={{ ...biv2H3, marginTop:24 }}>Your address</h3>
              <BIV2Field label="Street address" required fieldId="address_line1">
                <input value={state.address_line1} onChange={upd('address_line1')} style={biv2Input} placeholder="123 Main St" />
              </BIV2Field>
              <BIV2Field label="Apartment / suite (optional)" fieldId="address_line2">
                <input value={state.address_line2} onChange={upd('address_line2')} style={biv2Input} />
              </BIV2Field>
              <BIV2Field label="City" required fieldId="address_city">
                <input value={state.address_city} onChange={upd('address_city')} style={biv2Input} />
              </BIV2Field>
              <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
                <BIV2Field label="State" required fieldId="address_state">
                  <input value={state.address_state} onChange={(e)=>setState(s=>({...s,address_state:e.target.value.toUpperCase().slice(0,2)}))} style={biv2Input} maxLength={2} placeholder="NY" />
                </BIV2Field>
                <BIV2Field label="ZIP" required fieldId="address_zip">
                  <input value={state.address_zip} onChange={(e)=>setState(s=>({...s,address_zip:e.target.value.replace(/\D/g,'').slice(0,5)}))} style={biv2Input} inputMode="numeric" placeholder="10001" />
                </BIV2Field>
              </div>
              <BIV2Field label="Travel radius (miles)" hint="Maximum distance you'd travel to a student." fieldId="travel_radius_miles">
                <input type="number" min={0} max={200} value={state.travel_radius_miles} onChange={upd('travel_radius_miles')} style={{...biv2Input, maxWidth:120}} />
              </BIV2Field>
            </>
          )}
        </div>

        <div style={cardStyle}>
          <h3 style={biv2H3}>Education</h3>
          <label style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 0', cursor:'pointer', fontSize:14, color:'oklch(28% 0.05 265)', borderBottom: noEducation ? 'none' : '1px solid oklch(94% 0.01 265)', marginBottom: 14 }}>
            <input type="checkbox" checked={noEducation} onChange={e => setNoEducation(e.target.checked)} style={{ width:18, height:18 }} />
            <span>I'm not formally educated in this subject (self-taught, mentored, or experience-based).</span>
          </label>
          {!noEducation && (
            <>
              <BIV2Field label="Undergraduate college" fieldId="undergrad_college">
                <input value={state.undergrad_college} onChange={upd('undergrad_college')} style={biv2Input} placeholder="e.g. Berklee College of Music" />
              </BIV2Field>
              <BIV2Field label="Undergraduate major" fieldId="undergrad_major">
                <input value={state.undergrad_major} onChange={upd('undergrad_major')} style={biv2Input} placeholder="e.g. Music Performance" />
              </BIV2Field>
              <BIV2Field label="Graduate college (optional)" fieldId="grad_college_1">
                <input value={state.grad_college_1} onChange={upd('grad_college_1')} style={biv2Input} />
              </BIV2Field>
              {state.grad_college_1 && (
                <BIV2Field label="Graduate degree" fieldId="grad_degree_1">
                  <select value={state.grad_degree_1} onChange={upd('grad_degree_1')} style={biv2Input}>
                    <option value="">—</option><option>Master's</option><option>Doctorate</option><option>Professional</option><option>Certificate</option>
                  </select>
                </BIV2Field>
              )}
              <BIV2Field label="Teaching certification (optional)" fieldId="teaching_certification">
                <select value={state.teaching_certification} onChange={upd('teaching_certification')} style={biv2Input}>
                  <option value="">Not certified</option><option>State Certified</option><option>National Board Certified</option><option>Other</option>
                </select>
              </BIV2Field>
            </>
          )}
        </div>
      </div>
      {error && <div style={biv2ErrorBox}>{error}</div>}
      <BIV2NavBar onBack={() => setStep(2)} onNext={onNext} nextLabel="Save & continue" busy={busy} />
    </div>
  );
};

Object.assign(window, { BIV2Subjects, BIV2HowItWorks, BIV2BasicInfo });
