// Apply form (single-step) + Footer — Round 3
// Single-page form: name, email, phone, preferred contact method, procedure of interest.
// "Other" procedure expands a free-text input. Real-time inline validation.

// Replace with deployed Apps Script Web App URL when wiring Google Sheets.
// Submission gracefully no-ops if URL is empty (still shows confirmation).
const SHEETS_ENDPOINT = window.SANNATRIP_SHEETS_ENDPOINT || '';

// ---------- helpers ----------
const isEmail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((s || '').trim());
// Phone validation is delegated to intl-tel-input's iti.isValidNumber().

// ---------- Apply (single step) ----------
function Apply({ copy }) {
  const a = copy.apply;
  const isOther = (procVal, opts) => procVal && procVal === opts[opts.length - 1]; // last = "Other"

  const initialData = {
    name: '', email: '',
    phone: '',         // raw input — what the user types
    phoneE164: '',     // canonical E.164 from iti.getNumber()
    phoneCountry: 'US',// ISO2 from iti.getSelectedCountryData()
    phoneValid: false, // from iti.isValidNumber()
    contactMethod: '', procedure: '', otherProcedure: '',
    timeline: '', preferredLang: '', consent: false,
  };
  const [data, setData] = React.useState(initialData);
  const [touched, setTouched] = React.useState({});
  const [submitting, setSubmitting] = React.useState(false);
  const [submitted, setSubmitted] = React.useState(false);
  const [submitError, setSubmitError] = React.useState(false);

  // Map localized contact-method labels to canonical wire values.
  const contactCanonical = {
    'Email': 'Email',
    'Phone call': 'Phone', 'Llamada telefónica': 'Phone',
    'WhatsApp': 'WhatsApp',
  };

  // Map localized timeline labels to canonical wire codes.
  const timelineCanonical = {
    'Within 1 month':  '0-1m',      'En 1 mes':         '0-1m',
    '1–3 months':      '1-3m',      '1–3 meses':        '1-3m',
    '3–6 months':      '3-6m',      '3–6 meses':        '3-6m',
    '6+ months':       '6m+',       'Más de 6 meses':   '6m+',
    'Just exploring':  'exploring', 'Estoy explorando': 'exploring',
  };

  // Map localized specialty labels to stable snake_case codes.
  const specialtyCanonical = {
    'Cosmetic Surgery':              'cosmetic_surgery',
    'Cirugía estética':              'cosmetic_surgery',
    'Dental':                        'dental',
    'Odontología':                   'dental',
    'Bariatric':                     'bariatric',
    'Bariátrica':                    'bariatric',
    'Hip / Knee Replacement':        'hip_knee_replacement',
    'Reemplazo de cadera / rodilla': 'hip_knee_replacement',
    'Fertility (IVF)':               'fertility_ivf',
    'Fertilidad (FIV)':              'fertility_ivf',
    'Ophthalmic':                    'ophthalmic',
    'Oftálmica':                     'ophthalmic',
    'Hair Transplant':               'hair_transplant',
    'Implante Capilar':              'hair_transplant',
    'I am not sure yet':             'not_sure',
    'No estoy seguro':               'not_sure',
    'Other':                         'other',
    'Otro':                          'other',
  };

  const otherSelected = isOther(data.procedure, a.procOpts);

  // Field-level validation. Returns null if valid, else error key.
  const fieldErrors = React.useMemo(() => {
    const e = {};
    if (!data.name.trim()) e.name = 'required';
    if (!data.email.trim()) e.email = 'required';
    else if (!isEmail(data.email)) e.email = 'email';
    if (!data.phone.trim()) e.phone = 'required';
    else if (!data.phoneValid) e.phone = 'phone';
    if (!data.contactMethod) e.contactMethod = 'required';
    if (!data.procedure) e.procedure = 'required';
    if (otherSelected && !data.otherProcedure.trim()) e.otherProcedure = 'required';
    if (!data.timeline) e.timeline = 'required';
    if (!data.preferredLang) e.preferredLang = 'required';
    if (!data.consent) e.consent = 'consent';
    return e;
  }, [data, otherSelected]);

  const errorMsg = (key) => {
    const k = fieldErrors[key];
    if (!k) return null;
    return a.errors[k] || a.errors.required;
  };
  const showErr = (key) => touched[key] && !!fieldErrors[key];

  const isValid = Object.keys(fieldErrors).length === 0;

  const update = (patch) => setData(d => ({ ...d, ...patch }));
  const markTouched = (key) => setTouched(t => ({ ...t, [key]: true }));

  const submit = async () => {
    if (!isValid || submitting) return;
    setTouched({
      name: true, email: true, phone: true,
      contactMethod: true, procedure: true, otherProcedure: true,
      timeline: true, preferredLang: true, consent: true,
    });
    setSubmitting(true);
    setSubmitError(false);

    const specialty = otherSelected
      ? 'other'
      : (specialtyCanonical[data.procedure] || 'other');

    const payload = {
      full_name:                data.name.trim(),
      email:                    data.email.trim(),
      phone:                    data.phoneE164 || data.phone.trim(),
      specialty,
      ...(otherSelected ? { specialty_other: data.otherProcedure.trim() } : {}),
      timeline:                 timelineCanonical[data.timeline] || data.timeline,
      preferred_contact_method: contactCanonical[data.contactMethod] || data.contactMethod,
      preferred_language:       data.preferredLang === 'English' ? 'EN' : 'ES',
      privacy_consent:          !!data.consent,
      country:                  data.phoneCountry,
      source:                   'Landing Page',
    };

    try {
      await fetch(SHEETS_ENDPOINT, {
        method:  'POST',
        mode:    'no-cors',
        headers: { 'Content-Type': 'text/plain;charset=utf-8' },
        body:    JSON.stringify(payload),
      });
      // no-cors: response is opaque. Treat lack of exception as success.
      setData(initialData);
      setTouched({});
      setSubmitted(true);
    } catch (err) {
      console.warn('SannaTrip apply submit failed', err);
      setSubmitError(true);
    } finally {
      setSubmitting(false);
    }
  };

  // ---------- success state ----------
  if (submitted) {
    const msg = a.thanksTpl;
    return (
      <Section id="apply" pad="lg" bg={TOKENS.color.navy} style={{ color: '#fff' }}>
        <div style={{ maxWidth: 620, margin: '0 auto', textAlign: 'center', padding: '40px 0' }}>
          <div style={{
            width: 48, height: 48, margin: '0 auto 32px', borderRadius: '50%',
            border: `1px solid ${TOKENS.color.pink}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <svg width="20" height="16" viewBox="0 0 20 16" fill="none">
              <path d="M2 8l5 5L18 2" stroke={TOKENS.color.pink} strokeWidth="1.5" strokeLinecap="round" />
            </svg>
          </div>
          <Display size="md" style={{ color: '#fff' }}>{msg}</Display>
        </div>
      </Section>
    );
  }

  // ---------- form ----------
  return (
    <Section id="apply" pad="lg" bg={TOKENS.color.navy} style={{ color: '#fff' }}>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 'clamp(24px, 4vw, 64px)' }}>
        {/* Left: copy */}
        <div>
          <Eyebrow color="rgba(255,255,255,0.6)">{a.eyebrow}</Eyebrow>
          <div style={{ height: 16 }} />
          <Display size="lg" style={{ color: '#fff' }}>{a.title}</Display>
          <div style={{ height: 20 }} />
          <p style={{ margin: 0, fontFamily: TOKENS.font.sans, fontSize: 15, color: 'rgba(255,255,255,0.72)', lineHeight: 1.6, maxWidth: 420 }}>{a.sub}</p>
        </div>

        {/* Right: form */}
        <div style={{ background: TOKENS.color.ivory, padding: 'clamp(28px, 3vw, 44px)', color: TOKENS.color.ink }}>
          {/* Name */}
          <Field
            label={a.labels.name}
            error={showErr('name') ? errorMsg('name') : null}
          >
            <TextInput
              value={data.name}
              onChange={e => update({ name: e.target.value })}
              onBlur={() => markTouched('name')}
              placeholder={a.placeholders.name}
              invalid={showErr('name')}
            />
          </Field>

          {/* Email */}
          <Field
            label={a.labels.email}
            error={showErr('email') ? errorMsg('email') : null}
          >
            <TextInput
              type="email"
              value={data.email}
              onChange={e => update({ email: e.target.value })}
              onBlur={() => markTouched('email')}
              placeholder={a.placeholders.email}
              invalid={showErr('email')}
            />
          </Field>

          {/* Phone */}
          <Field
            label={a.labels.phone}
            error={showErr('phone') ? errorMsg('phone') : null}
          >
            <PhoneInput
              value={data.phone}
              onChange={({ raw, e164, country, valid }) => update({
                phone: raw, phoneE164: e164, phoneCountry: country, phoneValid: valid,
              })}
              onBlur={() => markTouched('phone')}
              placeholder={a.placeholders.phone}
              invalid={showErr('phone')}
            />
          </Field>

          {/* Procedure of interest */}
          <Field
            label={a.labels.procedure}
            error={showErr('procedure') ? errorMsg('procedure') : null}
          >
            <PillGroup
              options={a.procOpts}
              value={data.procedure}
              onChange={(v) => { update({ procedure: v }); markTouched('procedure'); }}
              invalid={showErr('procedure')}
            />
          </Field>

          {/* Other procedure (conditional) */}
          {otherSelected && (
            <div style={{ marginTop: -8, marginBottom: 24 }}>
              <TextInput
                value={data.otherProcedure}
                onChange={e => update({ otherProcedure: e.target.value })}
                onBlur={() => markTouched('otherProcedure')}
                placeholder={a.placeholders.other}
                invalid={showErr('otherProcedure')}
                autoFocus
              />
              {showErr('otherProcedure') && (
                <ErrorText>{errorMsg('otherProcedure')}</ErrorText>
              )}
            </div>
          )}

          {/* Timeline */}
          <Field
            label={a.labels.timeline}
            error={showErr('timeline') ? errorMsg('timeline') : null}
          >
            <PillGroup
              options={a.timelineOpts}
              value={data.timeline}
              onChange={(v) => { update({ timeline: v }); markTouched('timeline'); }}
              invalid={showErr('timeline')}
            />
          </Field>

          {/* Preferred contact method */}
          <Field
            label={a.labels.contact}
            error={showErr('contactMethod') ? errorMsg('contactMethod') : null}
          >
            <PillGroup
              options={a.contactOpts}
              value={data.contactMethod}
              onChange={(v) => { update({ contactMethod: v }); markTouched('contactMethod'); }}
              invalid={showErr('contactMethod')}
            />
          </Field>

          {/* Preferred language */}
          <Field
            label={a.labels.language}
            error={showErr('preferredLang') ? errorMsg('preferredLang') : null}
          >
            <PillGroup
              options={a.languageOpts}
              value={data.preferredLang}
              onChange={(v) => { update({ preferredLang: v }); markTouched('preferredLang'); }}
              invalid={showErr('preferredLang')}
            />
          </Field>

          {/* Consent */}
          <div style={{ marginBottom: 24 }}>
            <ConsentCheckbox
              value={data.consent}
              onChange={(v) => { update({ consent: v }); markTouched('consent'); }}
              text={a.consentText}
              policy={a.consentPolicy}
              policyHref={a.consentPolicyHref}
              invalid={showErr('consent')}
            />
            {showErr('consent') && <ErrorText>{errorMsg('consent')}</ErrorText>}
          </div>

          {/* Submit */}
          <div style={{ height: 8 }} />
          {submitError && (
            <div style={{ marginBottom: 16 }}>
              <ErrorText>{a.errors.submit}</ErrorText>
            </div>
          )}
          <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
            <Btn
              variant="accent"
              size="md"
              onClick={() => { if (isValid && !submitting) submit(); }}
              style={{
                opacity: (!isValid || submitting) ? 0.4 : 1,
                cursor: (!isValid || submitting) ? 'not-allowed' : 'pointer',
                pointerEvents: submitting ? 'none' : 'auto',
              }}
            >
              {submitting ? '…' : (submitError ? a.labels.retry : a.labels.submit)}
            </Btn>
          </div>
        </div>
      </div>
    </Section>
  );
}

// ---------- field wrapper ----------
function Field({ label, error, children }) {
  return (
    <div style={{ marginBottom: 24 }}>
      <FieldLabel>{label}</FieldLabel>
      {children}
      {error && <ErrorText>{error}</ErrorText>}
    </div>
  );
}

function FieldLabel({ children }) {
  return (
    <label style={{
      fontFamily: TOKENS.font.sans, fontSize: 11, fontWeight: 500,
      letterSpacing: '0.08em', textTransform: 'uppercase',
      color: TOKENS.color.inkMute, display: 'block', marginBottom: 4,
    }}>
      {children}
    </label>
  );
}

function ErrorText({ children }) {
  return (
    <div style={{
      fontFamily: TOKENS.font.sans, fontSize: 12, color: '#B8312F',
      marginTop: 6, letterSpacing: '0.01em',
    }}>
      {children}
    </div>
  );
}

// ---------- text input with invalid state ----------
function TextInput({ invalid, style, ...props }) {
  const baseBorder = invalid ? '#B8312F' : TOKENS.color.hairline;
  return (
    <input
      {...props}
      style={{
        width: '100%', padding: '12px 0', border: 'none',
        borderBottom: `1px solid ${baseBorder}`,
        fontFamily: TOKENS.font.sans, fontSize: 15,
        color: TOKENS.color.ink, background: 'transparent', outline: 'none',
        transition: 'border-color 160ms ease',
        ...(style || {}),
      }}
    />
  );
}

// ---------- phone input — intl-tel-input v23 (global, searchable, E.164) ----
// The wrapping .iti element holds the bottom hairline (see CSS in <head>).
// We feature-detect window.intlTelInput so Cloudflare Rocket Loader / lazy
// CDN delivery doesn't race the React mount.
function PhoneInput({ value, onChange, onBlur, placeholder, invalid }) {
  const inputRef = React.useRef(null);
  const itiRef   = React.useRef(null);
  const onChangeRef = React.useRef(onChange);
  React.useEffect(() => { onChangeRef.current = onChange; }, [onChange]);

  // Initialize once.
  React.useEffect(() => {
    const el = inputRef.current;
    if (!el) return;
    let cancelled = false;
    let cleanupFns = [];

    const boot = () => {
      if (cancelled || !window.intlTelInput) {
        if (!cancelled) setTimeout(boot, 50);
        return;
      }
      const iti = window.intlTelInput(el, {
        initialCountry: 'us',
        separateDialCode: true,
        countrySearch: true,
        formatAsYouType: true,
        // IP geolocation fallback — only used if initialCountry can't resolve.
        geoIpLookup: (success) => {
          fetch('https://ipapi.co/json/')
            .then(r => r.ok ? r.json() : Promise.reject())
            .then(d => success((d && d.country_code) ? d.country_code : 'us'))
            .catch(() => success('us'));
        },
        // utils is bundled into intlTelInputWithUtils.min.js (loaded in <head>),
        // so no loadUtils call is needed — isValidNumber + getNumber work immediately.
      });
      itiRef.current = iti;

      const emit = () => {
        const valid = !!(iti.isValidNumber && iti.isValidNumber());
        const country = (iti.getSelectedCountryData().iso2 || 'us').toUpperCase();
        const e164 = valid && iti.getNumber ? iti.getNumber() : '';
        onChangeRef.current && onChangeRef.current({
          raw: el.value, e164, country, valid,
        });
      };
      const onInput = () => emit();
      const onCountry = () => emit();
      el.addEventListener('input', onInput);
      el.addEventListener('countrychange', onCountry);
      cleanupFns.push(() => {
        el.removeEventListener('input', onInput);
        el.removeEventListener('countrychange', onCountry);
        try { iti.destroy(); } catch (_) {}
      });
      // Initial emit so country defaults propagate up.
      emit();
      // Re-emit once utils.js finishes loading so validation flips for any
      // value the user typed before utils was ready.
      if (iti.promise && typeof iti.promise.then === 'function') {
        iti.promise.then(() => { if (!cancelled) emit(); }).catch(() => {});
      }
    };
    boot();
    return () => { cancelled = true; cleanupFns.forEach(fn => fn()); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Toggle invalid class on the wrapper when the field-level error flips.
  React.useEffect(() => {
    const el = inputRef.current;
    if (!el) return;
    const wrap = el.closest('.iti');
    if (wrap) wrap.classList.toggle('is-invalid', !!invalid);
  }, [invalid]);

  return (
    <input
      ref={inputRef}
      className="st-tel"
      type="tel"
      autoComplete="tel"
      defaultValue={value || ''}
      onBlur={onBlur}
      placeholder={placeholder}
      data-st-iti=""
    />
  );
}

// Tag the .iti wrapper with our scoping class once it's been built.
// (intl-tel-input doesn't take a wrapperClass option; we add it post-init.)
if (typeof document !== 'undefined') {
  const tagWrapper = () => {
    document.querySelectorAll('input[data-st-iti]').forEach((el) => {
      const wrap = el.closest('.iti');
      if (wrap && !wrap.classList.contains('st-iti')) wrap.classList.add('st-iti');
    });
  };
  // Observe the form area for inserted .iti wrappers.
  const mo = new MutationObserver(tagWrapper);
  mo.observe(document.documentElement, { childList: true, subtree: true });
}

// ---------- pill option group ----------
function PillGroup({ options, value, onChange, invalid }) {
  return (
    <div style={{
      display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 8,
      ...(invalid ? { outline: 'none' } : {}),
    }}>
      {options.map(o => {
        const sel = value === o;
        return (
          <button
            key={o}
            type="button"
            onClick={() => onChange(o)}
            style={{
              textAlign: 'left', padding: '10px 14px',
              border: `1px solid ${sel ? TOKENS.color.navy : (invalid ? '#B8312F' : TOKENS.color.hairline)}`,
              background: sel ? TOKENS.color.navy : 'transparent',
              color: sel ? '#fff' : TOKENS.color.ink,
              fontFamily: TOKENS.font.sans, fontSize: 13, fontWeight: 450,
              cursor: 'pointer', transition: 'all 160ms ease',
            }}
          >
            {o}
          </button>
        );
      })}
    </div>
  );
}

// ---------- privacy consent checkbox ----------
// Renders `text` with the substring `{policy}` replaced by an <a href={policyHref}>.
// The checkbox button and the label are siblings so the link click does not toggle
// the check (a nested <a> inside a <button> is invalid markup).
function ConsentCheckbox({ value, onChange, text, policy, policyHref, invalid }) {
  const [prefix, suffix = ''] = (text || '').split('{policy}');
  const borderColor = value
    ? TOKENS.color.navy
    : (invalid ? '#B8312F' : TOKENS.color.hairline);
  return (
    <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
      <button
        type="button"
        role="checkbox"
        aria-checked={value}
        onClick={() => onChange(!value)}
        style={{
          flexShrink: 0, width: 16, height: 16, marginTop: 2,
          border: `1px solid ${borderColor}`,
          background: value ? TOKENS.color.navy : 'transparent',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', padding: 0,
          transition: 'all 160ms ease',
        }}
      >
        {value && (
          <svg width="10" height="8" viewBox="0 0 10 8" fill="none" aria-hidden="true">
            <path d="M1 4l3 3L9 1" stroke="#fff" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        )}
      </button>
      <label
        onClick={() => onChange(!value)}
        style={{
          fontFamily: TOKENS.font.sans, fontSize: 13,
          color: TOKENS.color.inkSoft, lineHeight: 1.5, cursor: 'pointer',
        }}
      >
        {prefix}
        <a
          href={policyHref}
          target="_blank"
          rel="noopener noreferrer"
          onClick={(e) => e.stopPropagation()}
          style={{
            color: TOKENS.color.navy,
            textDecoration: 'underline',
            textDecorationColor: TOKENS.color.pink,
            textUnderlineOffset: '3px',
          }}
        >
          {policy}
        </a>
        {suffix}
      </label>
    </div>
  );
}

// ---------- footer (unchanged) ----------
function Footer({ copy }) {
  return (
    <footer style={{ padding: 'clamp(40px, 5vw, 80px) clamp(20px, 5vw, 80px)', background: TOKENS.color.ivory, borderTop: `1px solid ${TOKENS.color.hairline}` }}>
      <div style={{ maxWidth: 1360, margin: '0 auto' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 40, flexWrap: 'wrap' }}>
          <div>
            <Wordmark />
            <div style={{ height: 12 }} />
            <div style={{ fontFamily: TOKENS.font.sans, fontSize: 13, color: TOKENS.color.inkMute }}>{copy.footer.tag}</div>
          </div>
          <div style={{ fontFamily: TOKENS.font.mono, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.1em', color: TOKENS.color.pinkDeep, textAlign: 'right' }}>{copy.footer.quiet}</div>
        </div>
        <div style={{ height: 48 }} />
        <div style={{ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 16, fontFamily: TOKENS.font.sans, fontSize: 12, color: TOKENS.color.inkMute }}>
          <span>© 2026 SannaTrip</span>
          <span>Bogotá · Barranquilla · Cali · Medellín</span>
        </div>
      </div>
    </footer>
  );
}

Object.assign(window, { Apply, Footer });
