/* global React, gsap, prefersReduced */
const {
  useState: useStateC,
  useRef: useRefC,
  useEffect: useEffectC,
  useLayoutEffect: useLayoutEffectC,
  useMemo: useMemoC,
} = React;

/* ------------------------------- Le parcours ------------------------------ */
/* Étape 1 : qui vous êtes (seuls champs requis).
   Étapes 2 & 3 : le brief, entièrement facultatif mais guidé par des choix
   pré-mâchés — on récolte beaucoup sans jamais imposer de rédaction.
   Étape 4 : cadre, champ libre de rattrapage et consentement. */
const STEPS = [
  { key: 'vous',   label: 'Vous',    title: 'Faisons connaissance', desc: 'Juste de quoi vous répondre.' },
  { key: 'projet', label: 'Projet',  title: 'Votre projet',         desc: 'Quelques repères pour cadrer le besoin — tout est facultatif.' },
  { key: 'style',  label: 'Style',   title: 'Direction artistique', desc: 'L’univers visuel que vous avez en tête. Ou pas encore, c’est très bien aussi.' },
  { key: 'cadre',  label: 'Cadre',   title: 'Budget & délai',       desc: 'Pour vous proposer quelque chose de réaliste.' },
];

/* --------------------------- Référentiels guidés -------------------------- */
/* `value` = clé stable envoyée à n8n · `label` = libellé affiché.
   Les mêmes listes sont revalidées côté serveur (server.js → /api/contact). */
const PROJECT_TYPES = [
  { value: 'site-vitrine', label: 'Site vitrine' },
  { value: 'e-commerce', label: 'E-commerce' },
  { value: 'application-web', label: 'Application web' },
  { value: 'refonte', label: 'Refonte' },
  { value: 'autre', label: 'Autre' },
];

const SITE_STATUS = [
  { value: 'refonte', label: 'Oui, un site à refondre' },
  { value: 'zero', label: 'Non, je pars de zéro' },
];

const MAIN_GOALS = [
  { value: 'gagner-clients', label: 'Gagner des clients' },
  { value: 'vendre-en-ligne', label: 'Vendre en ligne' },
  { value: 'presenter-activite', label: 'Présenter mon activité' },
  { value: 'moderniser-image', label: 'Moderniser mon image' },
  { value: 'automatiser', label: 'Automatiser un process' },
  { value: 'recruter', label: 'Recruter' },
];

const FEATURES = [
  { value: 'blog', label: 'Blog / actualités' },
  { value: 'rendez-vous', label: 'Prise de rendez-vous' },
  { value: 'paiement', label: 'Paiement en ligne' },
  { value: 'espace-client', label: 'Espace client' },
  { value: 'multilingue', label: 'Multilingue' },
  { value: 'seo', label: 'Référencement (SEO)' },
  { value: 'newsletter', label: 'Newsletter' },
  { value: 'dashboard', label: 'Tableau de bord' },
  { value: 'integration', label: 'Connexion à un outil existant' },
];

const LOGO_STATUS = [
  { value: 'existant', label: 'J’ai déjà un logo' },
  { value: 'a-creer', label: 'Un logo à créer' },
  { value: 'a-rafraichir', label: 'Un logo à rafraîchir' },
  { value: 'indecis', label: 'Pas encore décidé' },
];

const VIBES = [
  { value: 'epure', label: 'Épuré' },
  { value: 'premium', label: 'Premium' },
  { value: 'audacieux', label: 'Audacieux' },
  { value: 'chaleureux', label: 'Chaleureux' },
  { value: 'corporate', label: 'Corporate' },
  { value: 'creatif', label: 'Créatif' },
  { value: 'minimaliste', label: 'Minimaliste' },
  { value: 'ludique', label: 'Ludique' },
];

/* Échelles continues — l'utilisateur donne une fourchette, pas une case.
   `max` est ouvert vers le haut : atteindre la borne signifie « et plus ». */
const BUDGET_CFG = { min: 0, max: 15000, step: 500 };
const DELAY_CFG = { min: 0, max: 12, step: 1 };

const formatEuro = (v) =>
  v >= BUDGET_CFG.max
    ? `${BUDGET_CFG.max.toLocaleString('fr-FR')} € et +`
    : `${v.toLocaleString('fr-FR')} €`;

const formatMonths = (v) =>
  v <= 0 ? 'Dès que possible'
    : v >= DELAY_CFG.max ? `${DELAY_CFG.max} mois et +`
    : `${v} mois`;

const rangeLabel = (lo, hi, format) => (lo === hi ? format(lo) : `${format(lo)} — ${format(hi)}`);

const TEXT_MAX = 1500;
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i;

const EMPTY_FORM = {
  // Étape 1 — vous
  firstName: '', lastName: '', email: '', company: '', phone: '',
  // Étape 2 — projet
  projectType: '', siteStatus: '', existingSiteUrl: '', goals: [], features: [], references: '',
  // Étape 3 — direction artistique
  logoStatus: '', brandGuidelines: '', vibes: [],
  // Étape 4 — cadre. `*Set` distingue « laissé au défaut » de « réellement
  // renseigné » : sans interaction, la fourchette n'est pas envoyée.
  budgetMin: 2000, budgetMax: 6000, budgetSet: false,
  delayMin: 1, delayMax: 3, delaySet: false,
  extraInfo: '', consent: false,
  // Appât à bots — jamais visible ni rempli par un humain
  website: '',
};

/* Seuls ces champs sont requis ; tout le brief reste facultatif. */
const VALIDATORS = {
  firstName: (v) => (!v.trim() ? 'Merci d’indiquer votre prénom.' : ''),
  lastName: (v) => (!v.trim() ? 'Merci d’indiquer votre nom.' : ''),
  email: (v) =>
    !v.trim() ? 'Une adresse e-mail est nécessaire pour vous répondre.'
      : !EMAIL_RE.test(v.trim()) ? 'Cette adresse e-mail ne semble pas valide.'
      : '',
  consent: (v) => (!v ? 'Votre accord est nécessaire pour traiter la demande.' : ''),
};

/* Quel champ requis appartient à quelle étape — sert à bloquer le « Suivant ». */
const STEP_FIELDS = [['firstName', 'lastName', 'email'], [], [], ['consent']];

const validateAll = (values) =>
  Object.entries(VALIDATORS).reduce((acc, [field, fn]) => {
    const msg = fn(values[field]);
    if (msg) acc[field] = msg;
    return acc;
  }, {});

/* ---------------------------------- Icônes -------------------------------- */
const AlertIcon = ({ size = 14 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
       strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
       className="mt-[2px] flex-shrink-0" aria-hidden="true">
    <circle cx="12" cy="12" r="9" /><path d="M12 8v5" /><path d="M12 16h.01" />
  </svg>
);

/* --------------------------- Briques de formulaire ------------------------ */
/* Définies au niveau module (et non dans ContactForm) : une définition par rendu
   remonterait les inputs à chaque frappe et ferait perdre le focus. */
function Field({ id, label, optional, error, valid, hint, children, className = '' }) {
  const state = error ? ' is-error' : valid ? ' is-valid' : '';
  return (
    <div className={'ct-field ct-step-anim' + state + ' ' + className}>
      <label htmlFor={id} className="ct-label mb-2">
        {label}
        {optional && <span className="ct-optional"> — optionnel</span>}
      </label>
      {children}
      <div className="mt-1.5 flex min-h-[1.15rem] items-start justify-between gap-3">
        {error
          ? <p id={`${id}-error`} className="ct-err" role="alert"><AlertIcon />{error}</p>
          : <span />}
        {hint}
      </div>
    </div>
  );
}

/* Sélection par pastilles. `multi` autorise plusieurs réponses ; un second clic
   désélectionne toujours, pour qu'aucun choix ne soit irréversible. */
function ChipGroup({ id, label, options, value, onChange, multi = false, hint, className = '' }) {
  const selected = multi ? (value || []) : value;
  const toggle = (v) => {
    if (!multi) { onChange(selected === v ? '' : v); return; }
    onChange(selected.includes(v) ? selected.filter((x) => x !== v) : [...selected, v]);
  };
  return (
    <div className={'ct-field ct-step-anim ' + className} role="group" aria-labelledby={`${id}-label`}>
      <span id={`${id}-label`} className="ct-label mb-3">
        {label}<span className="ct-optional"> — optionnel</span>
      </span>
      <div className="flex flex-wrap gap-2.5">
        {options.map((o) => {
          const on = multi ? selected.includes(o.value) : selected === o.value;
          return (
            <button key={o.value} type="button" className="ct-chip" aria-pressed={on} onClick={() => toggle(o.value)}>
              <span className="ct-chip-mark" aria-hidden="true" />
              <span>{o.label}</span>
            </button>
          );
        })}
      </div>
      {hint && <p className="mt-2.5 text-[0.72rem] font-light leading-relaxed text-bone/30">{hint}</p>}
    </div>
  );
}

/* Échelle à deux poignées. Deux <input type="range"> superposés plutôt qu'un
   widget maison : on garde gratuitement le clavier, le tactile et les lecteurs
   d'écran. Les poignées ne peuvent pas se croiser. */
function RangeSlider({ id, label, cfg, value, active, onChange, format, hint }) {
  const [lo, hi] = value;
  const readoutRef = useRefC(null);
  const span = cfg.max - cfg.min;
  const pct = (v) => ((v - cfg.min) / span) * 100;

  // Légère pulsation du libellé à chaque cran franchi.
  useEffectC(() => {
    if (prefersReduced || !readoutRef.current) return;
    gsap.fromTo(readoutRef.current, { scale: 1.06 }, { scale: 1, duration: 0.3, ease: 'power2.out' });
  }, [lo, hi]);

  // Quand les deux poignées sont collées à la borne haute, celle du minimum doit
  // passer au-dessus — sinon plus rien n'est saisissable.
  const loOnTop = lo > cfg.max - span * 0.08;

  return (
    <div className="ct-field ct-step-anim" role="group" aria-labelledby={`${id}-label`}>
      <div className="mb-3 flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
        <span id={`${id}-label`} className="ct-label">
          {label}<span className="ct-optional"> — optionnel</span>
        </span>
        <span ref={readoutRef} className={'ct-readout' + (active ? ' is-active' : '')}>
          {active ? rangeLabel(lo, hi, format) : 'Non précisé'}
        </span>
      </div>

      <div className={'ct-range-wrap' + (active ? ' is-active' : '')}>
        <div className="ct-range-track" />
        <div
          className="ct-range-fill"
          style={{
            left: `calc(10px + (100% - 20px) * ${pct(lo) / 100})`,
            width: `calc((100% - 20px) * ${(pct(hi) - pct(lo)) / 100})`,
          }}
        />
        <input
          type="range" className="ct-range" style={{ zIndex: loOnTop ? 5 : 3 }}
          min={cfg.min} max={cfg.max} step={cfg.step} value={lo}
          onChange={(e) => onChange([Math.min(Number(e.target.value), hi), hi])}
          aria-label={`${label} — minimum`} aria-valuetext={format(lo)}
        />
        <input
          type="range" className="ct-range" style={{ zIndex: 4 }}
          min={cfg.min} max={cfg.max} step={cfg.step} value={hi}
          onChange={(e) => onChange([lo, Math.max(Number(e.target.value), lo)])}
          aria-label={`${label} — maximum`} aria-valuetext={format(hi)}
        />
      </div>

      <div className="ct-range-bounds mt-2">
        <span>{format(cfg.min)}</span>
        <span>{format(cfg.max)}</span>
      </div>
      {hint && <p className="mt-2.5 text-[0.72rem] font-light leading-relaxed text-bone/30">{hint}</p>}
    </div>
  );
}

/* ----------------------------- Écran de succès ---------------------------- */
function ContactSuccess({ firstName, onReset }) {
  const root = useRefC(null);

  useLayoutEffectC(() => {
    const el = root.current;
    if (!el || prefersReduced) return;
    const ctx = gsap.context(() => {
      const circle = el.querySelector('.ct-success-circle');
      const check = el.querySelector('.ct-success-check');
      gsap.fromTo(el.querySelectorAll('.ct-ok-anim'),
        { y: 22, opacity: 0 },
        { y: 0, opacity: 1, duration: 0.6, stagger: 0.09, ease: 'power3.out', delay: 0.35 });
      if (circle) {
        const len = circle.getTotalLength();
        gsap.fromTo(circle,
          { strokeDasharray: len, strokeDashoffset: len },
          { strokeDashoffset: 0, duration: 0.8, ease: 'power2.inOut' });
      }
      if (check) {
        const len = check.getTotalLength();
        gsap.fromTo(check,
          { strokeDasharray: len, strokeDashoffset: len },
          { strokeDashoffset: 0, duration: 0.45, ease: 'power2.out', delay: 0.55 });
      }
    }, el);
    return () => ctx.revert();
  }, []);

  return (
    <div ref={root} className="flex flex-col items-center px-2 py-10 text-center md:py-20">
      <svg width="96" height="96" viewBox="0 0 100 100" aria-hidden="true">
        <circle className="ct-success-stroke ct-success-circle" cx="50" cy="50" r="44"
                stroke="#7C9885" strokeWidth="2.5" />
        <path className="ct-success-stroke ct-success-check" d="M31 51.5 L44 64 L69 38"
              stroke="#7C9885" strokeWidth="4" />
      </svg>

      <h2 id="ct-ok-title" className="ct-ok-anim display-heading mt-9" style={{ fontSize: 'clamp(2rem, 6vw, 4rem)' }}>
        Message envoyé.
      </h2>
      <p className="ct-ok-anim mt-6 max-w-md font-light leading-relaxed text-bone/60"
         style={{ fontSize: 'clamp(0.95rem, 1.6vw, 1.1rem)' }}>
        Merci{firstName ? `, ${firstName}` : ''}. Votre demande est bien arrivée&nbsp;—
        je reviens vers vous sous 48&nbsp;heures ouvrées.
      </p>

      <button
        type="button"
        onClick={onReset}
        className="ct-ok-anim mt-11 text-xs font-light uppercase tracking-[0.2em] text-bone/40 underline decoration-bone/20 underline-offset-4 transition-colors duration-300 hover:text-sage"
        style={{ background: 'transparent', border: 'none', fontFamily: 'inherit' }}
      >
        Envoyer une autre demande
      </button>
    </div>
  );
}

/* -------------------------------- Page contact ---------------------------- */
function ContactForm() {
  const settings = window.__SITE_DATA__?.settings || {};
  const email = settings.email || 'contact@madpulse.fr';

  const [values, setValues] = useStateC(EMPTY_FORM);
  const [touched, setTouched] = useStateC({});
  const [step, setStep] = useStateC(0);
  const [maxStep, setMaxStep] = useStateC(0);
  const [status, setStatus] = useStateC('idle'); // idle · sending · success · error
  const [serverError, setServerError] = useStateC('');

  const rootRef = useRefC(null);   // pour ramener l'étape suivante dans le champ de vision
  const formRef = useRefC(null);
  const wrapRef = useRefC(null);   // conteneur dont la hauteur est interpolée
  const paneRef = useRefC(null);   // contenu de l'étape courante
  const sendingRef = useRefC(false);  // garde anti double-soumission, synchrone
  const animatingRef = useRefC(false);
  const mountedRef = useRefC(false);
  const dirRef = useRefC(1);       // 1 = on avance, -1 = on recule
  const prevHRef = useRefC(0);

  const sending = status === 'sending';
  const isLast = step === STEPS.length - 1;

  const errors = useMemoC(() => validateAll(values), [values]);
  const showError = (field) => (touched[field] ? errors[field] || '' : '');
  const isValid = (field) => touched[field] && !errors[field];

  /* Transition d'étape : le panneau entre en glissant dans le sens de la
     navigation pendant que la hauteur du conteneur est interpolée, ce qui évite
     le saut brutal entre deux étapes de tailles différentes. */
  useLayoutEffectC(() => {
    const pane = paneRef.current;
    const wrap = wrapRef.current;
    if (!pane || !wrap) return; // absents dès que l'écran de succès a pris la place
    if (prefersReduced) { mountedRef.current = true; animatingRef.current = false; return; }

    // Au premier rendu, le panneau est simplement posé : c'est le FadeIn de la
    // section qui porte l'apparition. Sans ça, l'animation se jouerait pendant
    // que la section est encore hors écran.
    if (!mountedRef.current) {
      mountedRef.current = true;
      gsap.set(pane, { x: 0, opacity: 1 });
      return;
    }

    const ctx = gsap.context(() => {
      const tl = gsap.timeline({
        onComplete: () => { gsap.set(wrap, { height: 'auto' }); animatingRef.current = false; },
      });
      if (prevHRef.current) {
        tl.fromTo(wrap, { height: prevHRef.current }, { height: pane.offsetHeight, duration: 0.45, ease: 'power3.inOut' }, 0);
      }
      tl.fromTo(pane,
        { x: dirRef.current * 40, opacity: 0 },
        { x: 0, opacity: 1, duration: 0.45, ease: 'power3.out' }, 0);
      tl.fromTo(pane.querySelectorAll('.ct-step-anim'),
        { y: 18, opacity: 0 },
        { y: 0, opacity: 1, duration: 0.45, stagger: 0.05, ease: 'power3.out' }, 0.08);
    }, pane);

    mountedRef.current = true;
    return () => ctx.revert();
  }, [step]);

  /* Après un changement d'étape, le haut du formulaire peut se retrouver au-dessus
     de la fenêtre — on le ramène à l'écran plutôt que de laisser l'utilisateur
     face au bas d'un formulaire qui a changé sous ses yeux. */
  const ramenerEnVue = () => {
    const el = rootRef.current;
    if (!el) return;
    const haut = el.getBoundingClientRect().top;
    if (haut > 0) return;
    window.scrollTo({ top: window.scrollY + haut - 90, behavior: prefersReduced ? 'auto' : 'smooth' });
  };

  const setField = (field) => (e) => {
    const v = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
    setValues((prev) => ({ ...prev, [field]: v }));
    if (serverError) setServerError('');
  };
  const setValue = (field) => (v) => setValues((prev) => ({ ...prev, [field]: v }));
  const markTouched = (field) => () => setTouched((prev) => ({ ...prev, [field]: true }));

  /* Vérifie les champs requis de l'étape ; marque et cible le premier fautif. */
  const stepIsComplete = (i) => STEP_FIELDS[i].every((f) => !errors[f]);
  const enforceStep = (i) => {
    if (stepIsComplete(i)) return true;
    setTouched((prev) => STEP_FIELDS[i].reduce((a, f) => ({ ...a, [f]: true }), { ...prev }));
    const firstBad = STEP_FIELDS[i].find((f) => errors[f]);
    formRef.current?.querySelector(`[name="${firstBad}"]`)?.focus();
    return false;
  };

  const goToStep = (next) => {
    if (animatingRef.current || next === step || next < 0 || next >= STEPS.length) return;
    if (next > step) {
      // On ne saute pas par-dessus une étape dont les champs requis manquent.
      for (let i = step; i < next; i++) if (!enforceStep(i)) return;
    }
    dirRef.current = next > step ? 1 : -1;
    prevHRef.current = wrapRef.current ? wrapRef.current.offsetHeight : 0;
    setMaxStep((m) => Math.max(m, next));

    if (prefersReduced) { setStep(next); return; }
    animatingRef.current = true;
    gsap.to(paneRef.current, {
      x: -dirRef.current * 40,
      opacity: 0,
      duration: 0.26,
      ease: 'power2.in',
      onComplete: () => setStep(next),
    });
  };

  /* Entrée valide l'étape plutôt que le formulaire, sauf sur la dernière.
     (Dans un textarea, Entrée reste un saut de ligne.) */
  const onFormKeyDown = (e) => {
    if (e.key !== 'Enter' || isLast) return;
    if (e.target.tagName === 'TEXTAREA' || e.target.type === 'submit') return;
    e.preventDefault();
    goToStep(step + 1);
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (sendingRef.current) return; // double clic / double Entrée

    const found = validateAll(values);
    if (Object.keys(found).length) {
      // Ramène l'utilisateur sur la première étape encore incomplète.
      const badStep = STEP_FIELDS.findIndex((fields) => fields.some((f) => found[f]));
      setTouched(Object.keys(VALIDATORS).reduce((a, k) => ({ ...a, [k]: true }), {}));
      if (badStep >= 0 && badStep !== step) goToStep(badStep);
      else enforceStep(step);
      return;
    }

    sendingRef.current = true;
    setStatus('sending');
    setServerError('');

    try {
      const res = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(values),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || 'L’envoi a échoué.');
      setStatus('success');
    } catch (err) {
      // Les saisies sont conservées : l'utilisateur peut réessayer tel quel.
      setServerError(
        err instanceof TypeError
          ? 'Connexion impossible. Vérifiez votre réseau puis réessayez.'
          : err.message
      );
      setStatus('error');
    } finally {
      sendingRef.current = false;
    }
  };

  const resetForm = () => {
    setValues(EMPTY_FORM);
    setTouched({});
    setServerError('');
    setStatus('idle');
    setStep(0);
    setMaxStep(0);
    mountedRef.current = false;
  };

  const current = STEPS[step];
  const extraLen = values.extraInfo.trim().length;

  return (
    <div ref={rootRef} className="ct-page w-full text-left">
      {status === 'success' ? (
        <ContactSuccess firstName={values.firstName.trim()} onReset={resetForm} />
      ) : (
        <>
              {/* Progression */}
              <div>
                <div className="mb-3 flex items-center justify-between gap-4">
                  {STEPS.map((st, i) => (
                    <button
                      key={st.key}
                      type="button"
                      onClick={() => goToStep(i)}
                      disabled={i > maxStep}
                      className={'ct-stepname' + (i === step ? ' is-current' : i <= maxStep ? ' is-done' : '')}
                    >
                      <span className="tabular-nums">{String(i + 1).padStart(2, '0')}</span>
                      <span className="ml-2 hidden sm:inline">{st.label}</span>
                    </button>
                  ))}
                </div>
                <div className="ct-progress">
                  <span style={{ width: `${((step + 1) / STEPS.length) * 100}%` }} />
                </div>
              </div>

              <form
                ref={formRef}
                onSubmit={handleSubmit}
                onKeyDown={onFormKeyDown}
                noValidate
                className="mt-8 rounded-[28px] border-2 border-edge bg-night2/50 p-5 sm:p-7 md:rounded-[40px] md:p-10"
              >
                {/* Honeypot : masqué aux humains et aux lecteurs d'écran, appât à bots */}
                <div aria-hidden="true" style={{ position: 'absolute', left: '-9999px', width: 1, height: 1, overflow: 'hidden' }}>
                  <label htmlFor="ct-website">Ne pas remplir</label>
                  <input id="ct-website" name="website" type="text" tabIndex={-1} autoComplete="off"
                         value={values.website} onChange={setField('website')} />
                </div>

                <div ref={wrapRef} className="ct-steps-wrap">
                  <div ref={paneRef} className="ct-pane">
                    <div className="ct-step-anim mb-7">
                      <h2 className="text-bone" style={{ fontSize: 'clamp(1.15rem, 2.4vw, 1.5rem)', fontWeight: 500, letterSpacing: '-0.01em' }}>
                        {current.title}
                      </h2>
                      <p className="mt-1.5 text-sm font-light leading-relaxed text-bone/45">{current.desc}</p>
                    </div>

                    {/* ---------------------- Étape 1 — Vous ---------------------- */}
                    {step === 0 && (
                      <div className="grid gap-5 sm:grid-cols-2">
                        <Field id="ct-firstName" label="Prénom" error={showError('firstName')} valid={isValid('firstName')}>
                          <input
                            id="ct-firstName" name="firstName" type="text" className="ct-input" maxLength={80}
                            placeholder="Camille" autoComplete="given-name" required
                            value={values.firstName} onChange={setField('firstName')} onBlur={markTouched('firstName')}
                            aria-invalid={!!showError('firstName')}
                            aria-describedby={showError('firstName') ? 'ct-firstName-error' : undefined}
                          />
                        </Field>

                        <Field id="ct-lastName" label="Nom" error={showError('lastName')} valid={isValid('lastName')}>
                          <input
                            id="ct-lastName" name="lastName" type="text" className="ct-input" maxLength={80}
                            placeholder="Martin" autoComplete="family-name" required
                            value={values.lastName} onChange={setField('lastName')} onBlur={markTouched('lastName')}
                            aria-invalid={!!showError('lastName')}
                            aria-describedby={showError('lastName') ? 'ct-lastName-error' : undefined}
                          />
                        </Field>

                        <Field id="ct-email" label="Adresse e-mail" error={showError('email')} valid={isValid('email')}>
                          <input
                            id="ct-email" name="email" type="email" className="ct-input" maxLength={180}
                            placeholder="camille@entreprise.fr" autoComplete="email" required
                            value={values.email} onChange={setField('email')} onBlur={markTouched('email')}
                            aria-invalid={!!showError('email')}
                            aria-describedby={showError('email') ? 'ct-email-error' : undefined}
                          />
                        </Field>

                        <Field id="ct-phone" label="Téléphone" optional>
                          <input
                            id="ct-phone" name="phone" type="tel" className="ct-input" maxLength={40}
                            placeholder="06 12 34 56 78" autoComplete="tel"
                            value={values.phone} onChange={setField('phone')}
                          />
                        </Field>

                        <Field id="ct-company" label="Entreprise" optional className="sm:col-span-2">
                          <input
                            id="ct-company" name="company" type="text" className="ct-input" maxLength={120}
                            placeholder="Nom de la société" autoComplete="organization"
                            value={values.company} onChange={setField('company')}
                          />
                        </Field>
                      </div>
                    )}

                    {/* --------------------- Étape 2 — Projet --------------------- */}
                    {step === 1 && (
                      <div className="flex flex-col gap-7">
                        <ChipGroup id="ct-projectType" label="Type de projet" options={PROJECT_TYPES}
                                   value={values.projectType} onChange={setValue('projectType')} />

                        <ChipGroup id="ct-siteStatus" label="Un site existe déjà ?" options={SITE_STATUS}
                                   value={values.siteStatus} onChange={setValue('siteStatus')} />

                        {values.siteStatus === 'refonte' && (
                          <Field id="ct-existingSiteUrl" label="Adresse du site actuel" optional className="ct-reveal">
                            <input
                              id="ct-existingSiteUrl" name="existingSiteUrl" type="url" className="ct-input" maxLength={300}
                              placeholder="https://monsite.fr" autoComplete="url" inputMode="url"
                              value={values.existingSiteUrl} onChange={setField('existingSiteUrl')}
                            />
                          </Field>
                        )}

                        <ChipGroup id="ct-goals" label="Objectif principal" multi options={MAIN_GOALS}
                                   value={values.goals} onChange={setValue('goals')}
                                   hint="Plusieurs réponses possibles." />

                        <ChipGroup id="ct-features" label="Fonctionnalités attendues" multi options={FEATURES}
                                   value={values.features} onChange={setValue('features')}
                                   hint="Même une idée approximative m’aide à estimer la charge." />

                        <Field id="ct-references" label="Des sites qui vous plaisent" optional>
                          <textarea
                            id="ct-references" name="references" className="ct-textarea" maxLength={TEXT_MAX}
                            style={{ minHeight: 100 }}
                            placeholder="Collez une ou plusieurs adresses, et ce qui vous plaît dedans."
                            value={values.references} onChange={setField('references')}
                          />
                        </Field>
                      </div>
                    )}

                    {/* ---------------- Étape 3 — Direction artistique ------------- */}
                    {step === 2 && (
                      <div className="flex flex-col gap-7">
                        <ChipGroup id="ct-logoStatus" label="Votre logo" options={LOGO_STATUS}
                                   value={values.logoStatus} onChange={setValue('logoStatus')} />

                        <Field id="ct-brandGuidelines" label="Charte graphique existante" optional>
                          <input
                            id="ct-brandGuidelines" name="brandGuidelines" type="text" className="ct-input" maxLength={300}
                            placeholder="Couleurs, polices, lien vers la charte…"
                            value={values.brandGuidelines} onChange={setField('brandGuidelines')}
                          />
                        </Field>

                        <ChipGroup id="ct-vibes" label="Le ton général" multi options={VIBES}
                                   value={values.vibes} onChange={setValue('vibes')} />
                      </div>
                    )}

                    {/* ------------------ Étape 4 — Cadre & envoi ------------------ */}
                    {step === 3 && (
                      <div className="flex flex-col gap-7">
                        <RangeSlider
                          id="ct-budget" label="Budget estimé" cfg={BUDGET_CFG} format={formatEuro}
                          value={[values.budgetMin, values.budgetMax]} active={values.budgetSet}
                          onChange={([lo, hi]) => setValues((v) => ({ ...v, budgetMin: lo, budgetMax: hi, budgetSet: true }))}
                          hint="Glissez les deux poignées pour donner une fourchette, même approximative."
                        />

                        <RangeSlider
                          id="ct-delay" label="Délai souhaité" cfg={DELAY_CFG} format={formatMonths}
                          value={[values.delayMin, values.delayMax]} active={values.delaySet}
                          onChange={([lo, hi]) => setValues((v) => ({ ...v, delayMin: lo, delayMax: hi, delaySet: true }))}
                        />

                        <Field
                          id="ct-extraInfo" label="Informations supplémentaires" optional
                          hint={<span className={'ct-counter' + (extraLen > TEXT_MAX - 200 ? ' is-near' : '')}>{extraLen} / {TEXT_MAX}</span>}
                        >
                          <textarea
                            id="ct-extraInfo" name="extraInfo" className="ct-textarea" maxLength={TEXT_MAX}
                            placeholder="Tout ce que les questions précédentes n’ont pas couvert : contraintes, contexte, échéance particulière, question précise…"
                            value={values.extraInfo} onChange={setField('extraInfo')}
                          />
                        </Field>

                        {/* Consentement RGPD */}
                        <div className={'ct-field ct-step-anim' + (showError('consent') ? ' is-error' : '')}>
                          <div className="flex items-start gap-3">
                            <input
                              id="ct-consent" name="consent" type="checkbox" className="ct-check" required
                              checked={values.consent} onChange={setField('consent')} onBlur={markTouched('consent')}
                              aria-invalid={!!showError('consent')}
                              aria-describedby={showError('consent') ? 'ct-consent-error' : undefined}
                            />
                            <label htmlFor="ct-consent" className="text-[0.82rem] font-light leading-relaxed text-bone/55">
                              J’accepte que les informations transmises soient utilisées pour me recontacter
                              au sujet de ma demande. Elles ne sont ni cédées ni revendues, et sont
                              supprimées sur simple demande à{' '}
                              <a href={`mailto:${email}`} className="text-sage underline decoration-sage/40 underline-offset-4 transition-colors duration-300 hover:decoration-sage">
                                {email}
                              </a>.
                            </label>
                          </div>
                          <div className="mt-1.5 min-h-[1.15rem] pl-8">
                            {showError('consent') && (
                              <p id="ct-consent-error" className="ct-err" role="alert"><AlertIcon />{showError('consent')}</p>
                            )}
                          </div>
                        </div>

                        {/* Échec réseau / serveur — les saisies restent en place */}
                        {status === 'error' && serverError && (
                          <div className="ct-alert" role="alert">
                            <AlertIcon size={16} />
                            <span>{serverError}</span>
                          </div>
                        )}
                      </div>
                    )}
                  </div>
                </div>

                {/* Navigation entre étapes */}
                <div className="mt-9 flex items-center justify-between gap-4 border-t border-[rgba(232,237,233,0.08)] pt-7">
                  {step > 0 ? (
                    <button type="button" className="ct-back" onClick={() => goToStep(step - 1)} disabled={sending}>
                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                           strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                        <path d="M19 12H5" /><path d="M12 19l-7-7 7-7" />
                      </svg>
                      <span className="hidden sm:inline">Retour</span>
                    </button>
                  ) : <span />}

                  {isLast ? (
                    <button type="submit" className="ct-submit is-inline" disabled={sending}>
                      {sending
                        ? <><span className="ct-spinner" /><span>Envoi en cours…</span></>
                        : <><span>{status === 'error' ? 'Réessayer' : 'Envoyer ma demande'}</span>
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                                 strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                              <path d="M7 17L17 7" /><path d="M7 7h10v10" />
                            </svg></>}
                    </button>
                  ) : (
                    <button type="button" className="ct-submit is-inline" onClick={() => goToStep(step + 1)}>
                      <span>{step === 0 ? 'Continuer' : 'Suivant'}</span>
                      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                           strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                        <path d="M5 12h14" /><path d="M12 5l7 7-7 7" />
                      </svg>
                    </button>
                  )}
                </div>

                {/* Statut annoncé aux lecteurs d'écran */}
                <p className="sr-only" role="status" aria-live="polite">
                  {sending ? 'Envoi du formulaire en cours.'
                    : status === 'error' ? 'L’envoi a échoué.'
                    : `Étape ${step + 1} sur ${STEPS.length} : ${current.title}.`}
                </p>
              </form>

        </>
      )}
    </div>
  );
}

Object.assign(window, { ContactForm });
