/* global React, Icon, Button, Kicker, InfoBox, Field, Input, Textarea, Checkbox, ProductImg, PlaceIcon, ProtectedEmail, decodeEmail, Captcha */
// OrderPage — /commander  (3-step form)

const PLACES = [
{ id: "boutique", icon: "store", kind: "Boutique", name: "Boutique de l'exploitation", address: "1 La Petite Canau, 33590 Saint-Vivien-de-Médoc", days: ["Ven.", "Sam."] },
{ id: "saintvivien", icon: "market", kind: "Marché merc.", name: "Marché de Saint-Vivien", address: "Place de la Brigade Carnot, 33590 Saint-Vivien-de-Médoc", days: ["Mer."] },
{ id: "eysines", icon: "market", kind: "Marché dim.", name: "Marché de Migron, Eysines", address: "Place Florale, 33320 Eysines", days: ["Dim."] }];


// ─────────────────────────────────────────────────────────────────────────────
// Catalogue produits — prix toujours en €/kg. avgKg = poids moyen d'une pièce.
// ─────────────────────────────────────────────────────────────────────────────
const PRODUCTS = {
  "Huîtres Gigas Meduli": [
  { id: "n2", sheetId: "HGM2", name: "Gigas Meduli n°2", subtitle: "La Méga", hint: "85 à 120 g/pièce · ≈ 10 au kg", units: ["pièce", "douzaine", "kg"], price: 11, avgKg: 0.100, maxKg: 20, icon: "oyster" },
  { id: "n3", sheetId: "HGM3", name: "Gigas Meduli n°3", subtitle: "Résistance", hint: "75 à 85 g/pièce · ≈ 12 au kg", units: ["pièce", "douzaine", "kg"], price: 11, avgKg: 0.080, maxKg: 20, icon: "oyster" },
  { id: "n4", sheetId: "HGM4", name: "Gigas Meduli n°4", subtitle: "L'entrée des artistes", hint: "45 à 75 g/pièce · ≈ 16-18 au kg", units: ["pièce", "douzaine", "kg"], price: 11, avgKg: 0.060, maxKg: 20, icon: "oyster" },
  { id: "n5", sheetId: "HGM5", name: "Gigas Meduli n°5", subtitle: "Miss Cocktail", hint: "30 à 45 g/pièce · ≈ 22-24 au kg", units: ["pièce", "douzaine", "kg"], price: 11, avgKg: 0.040, maxKg: 20, icon: "oyster" },
  { id: "ndc", sheetId: "HGMD", name: "Huîtres déclassées", subtitle: "Calibre variable", hint: "Même goût, possible chambre*", units: ["pièce", "douzaine", "kg"], price: 9, avgKg: 0.080, maxKg: 20, icon: "oyster", footnote: "* La chambre est un petit dépôt à l'intérieur de l'huître, recouvert de nacre par le coquillage pour s'en protéger. Naturel et inoffensif, mais si la nacre est percée à l'ouverture, une légère odeur peut s'en dégager." }],

  "Coquillages vivants": [
  { id: "palourdes", sheetId: "PALO", name: "Palourdes", hint: "1 pelle ≈ 300 g · Purifiées et dégorgées", units: ["g", "kg"], price: 27, avgKg: 0, maxKg: 5, icon: "shells" },
  { id: "praires", sheetId: "PRAI", name: "Praires", hint: "≈ 40 g/pièce · Purifiées et dégorgées", units: ["pièce", "g", "kg"], price: 18, avgKg: 0.040, maxKg: 5, icon: "shells" },
  { id: "tellines", sheetId: "TELL", name: "Tellines", hint: "1 pelle ≈ 200-300 g · Purifiées et dégorgées", units: ["g", "kg"], price: 25, avgKg: 0, maxKg: 5, icon: "shells" },
  { id: "lavagnons", sheetId: "LAVA", name: "Lavagnons", hint: "1 pelle ≈ 200-300 g · Purifiées et dégorgées", units: ["g", "kg"], price: 17, avgKg: 0, maxKg: 5, icon: "shells" },
  { id: "bigo-v", sheetId: "BIGOV", name: "Bigorneaux vivants", hint: "1 pelle ≈ 300 g · À cuire soi-même", units: ["g", "kg"], price: 15, avgKg: 0, maxKg: 5, icon: "bigorneaux" }],

  "Cuissons maison": [
  { id: "bigo-c", sheetId: "BIGOC", name: "Bigorneaux cuisson maison", hint: "1 pelle ≈ 300 g · Court-bouillon parfumé", units: ["g", "kg"], price: 25, avgKg: 0, maxKg: 3, icon: "bigorneaux", cuisson: true },
  { id: "bulots", sheetId: "BULOC", name: "Bulots cuisson maison", hint: "1 pelle ≈ 200 g · Court-bouillon parfumé", units: ["g", "kg"], price: 21, avgKg: 0, maxKg: 3, icon: "bulot", cuisson: true },
  { id: "tourteaux", sheetId: "TOURT", name: "Tourteaux cuisson maison", hint: "1 pièce ≈ 400 à 500 g · Court-bouillon parfumé", units: ["pièce"], price: 22, avgKg: 0.450, maxKg: 3, icon: "crab", cuisson: true },
  { id: "crev-b", sheetId: "CREVB", name: "Crevettes blanches", hint: "1 pelle ≈ 100 g · Pauillac", units: ["g"], price: 35, avgKg: 0, maxKg: 1, icon: "shrimp" },
  { id: "gw", sheetId: "GAMW", name: "Gambas White Tiger cuisson maison", hint: "≈ 4 pièces/100 g · Court-bouillon parfumé", units: ["pièce", "g", "kg"], price: 29, avgKg: 0.025, maxKg: 2, icon: "gambas", cuisson: true },
  { id: "gb", sheetId: "GAMB", name: "Gambas Black Tiger cuisson maison", hint: "≈ 2-3 pièces/100 g · Court-bouillon parfumé", units: ["pièce", "g", "kg"], price: 42, avgKg: 0.040, maxKg: 2, icon: "gambas", cuisson: true },
  { id: "araignees", sheetId: "ARAIG", name: "Araignées cuisson maison", hint: "1 pièce ≈ 1 à 1,5 kg · Court-bouillon parfumé", units: ["pièce"], price: 17, avgKg: 1.250, maxKg: 6, icon: "crab", cuisson: true },
  { id: "etrilles", sheetId: "ETRIL", name: "Étrilles cuisson maison", hint: "1 pièce ≈ 70-200 g · Court-bouillon parfumé", units: ["pièce", "g", "kg"], price: 28, avgKg: 0.135, maxKg: 3, icon: "crab", cuisson: true },
  { id: "crabes-v", sheetId: "CRABV", name: "Crabes verts cuisson maison", hint: "1 pièce ≈ 30 g · Court-bouillon parfumé", units: ["pièce", "g", "kg"], price: 12, avgKg: 0.030, maxKg: 2, icon: "crab", cuisson: true },
  { id: "homards", sheetId: "HOM", name: "Homards cuisson maison", hint: "1 pièce ≈ 400-500 g · Court-bouillon parfumé", units: ["pièce"], price: 47, avgKg: 0.450, maxKg: 3, icon: "lobster", cuisson: true },
  { id: "homards-e", sheetId: "HOME", name: "Homards épatés cuisson maison", hint: "400-500 g/pc · Pince en moins ou plus petite", units: ["pièce"], price: 42, avgKg: 0.450, maxKg: 3, icon: "lobster", cuisson: true }]

};

const ALL_PRODUCTS = Object.values(PRODUCTS).flat();
const findProduct = (id) => ALL_PRODUCTS.find((p) => p.id === id);

// ─────────────────────────────────────────────────────────────────────────────────
// Google Sheets : dispos hebdomadaires
//
// COMMENT BRANCHER VOTRE SHEET :
//  1. Dans Google Sheets, ouvrir le document. Menu Fichier > Partager > Publier
//     sur le web. Choisir la feuille concernée, format « Valeurs séparées par
//     des virgules (.csv) », puis cliquer Publier.
//  2. Copier l'URL générée (de la forme :
//       https://docs.google.com/spreadsheets/d/e/2PACX-…/pub?output=csv)
//     et la coller ci-dessous dans AVAILABILITY_CSV_URL.
//
// Format attendu du sheet, une ligne par produit, colonnes par position :
//   colonne 0 = identifiant (sheetId)
//   colonne 1 = « oui » ou « non » (insensible à la casse)
//   colonne 2 = libellé (ignoré, juste pour vous y retrouver dans le sheet)
//
//   BIGOC, oui, Bigorneaux cuisson maison
//   BIGOV, oui, Bigorneaux vivants
//   …
//
// Les produits dont la 2e colonne vaut « non » sont masqués pour la semaine
// en cours. Tant que l'URL est vide ou que la requête échoue, tous les
// produits restent visibles (fail-open, pour éviter un formulaire vide).
// ─────────────────────────────────────────────────────────────────────────────────
const AVAILABILITY_CSV_URL = ""; // ← à remplir avec l'URL CSV publiée

function parseAvailabilityCSV(text) {
  const map = {};
  if (!text) return map;
  for (const rawLine of text.split(/\r?\n/)) {
    const line = rawLine.trim();
    if (!line) continue;
    const cols = line.split(",").map((c) => c.trim().replace(/^"|"$/g, ""));
    const key = (cols[0] || "").toUpperCase();
    const flag = (cols[1] || "").toLowerCase();
    if (!key) continue;
    map[key] = flag !== "non" && flag !== "no" && flag !== "false" && flag !== "0";
  }
  return map;
}

// Hook : retourne { ready, available }. Tant que l'URL n'est pas configurée
// (ou que la requête échoue), on renvoie une map vide et tout est affiché.
function useAvailability() {
  const [state, setState] = React.useState({ ready: !AVAILABILITY_CSV_URL, available: {} });
  React.useEffect(() => {
    if (!AVAILABILITY_CSV_URL) return;
    let cancelled = false;
    // Cache-bust : Google ne renvoie pas toujours les bons en-têtes de cache,
    // donc on force un querystring unique pour avoir la dernière version du sheet.
    const url = AVAILABILITY_CSV_URL + (AVAILABILITY_CSV_URL.includes("?") ? "&" : "?") + "_=" + Date.now();
    fetch(url)
      .then((r) => r.ok ? r.text() : Promise.reject(new Error("HTTP " + r.status)))
      .then((csv) => { if (!cancelled) setState({ ready: true, available: parseAvailabilityCSV(csv) }); })
      .catch((err) => {
        console.warn("[order] dispos sheet inaccessible, tout est affiché par défaut :", err);
        if (!cancelled) setState({ ready: true, available: {} });
      });
    return () => { cancelled = true; };
  }, []);
  return state;
}

// Renvoie true si le produit est disponible cette semaine selon la map
// reçue. Si la map est vide (sheet pas configuré, requête échouée), on
// laisse tout passer.
function isAvailable(product, availabilityMap) {
  if (!availabilityMap || Object.keys(availabilityMap).length === 0) return true;
  const key = (product.sheetId || "").toUpperCase();
  if (!key) return true;
  // produit présent dans le sheet → on respecte la valeur.
  // produit absent du sheet → on considère disponible (fail-open).
  return availabilityMap[key] !== false;
}

// Unit handling ─────────────────────────────────────────────
function pluralize(unit, qty) {
  if (unit === "g" || unit === "kg") return unit;
  const n = Number(qty) || 0;
  if (n <= 1) return unit;
  return unit + "s";
}
function unitDefault(unit) {
  if (unit === "g") return 200;
  if (unit === "kg") return 1;
  return 1;
}
function unitStep(unit) {
  if (unit === "g") return 50;
  if (unit === "kg") return 0.5;
  return 1;
}
function unitMin(unit) {
  if (unit === "g") return 50;
  if (unit === "kg") return 0.5;
  return 1;
}
function totalKg(unit, qty, avgKg) {
  const n = Number(qty) || 0;
  if (unit === "g") return n / 1000;
  if (unit === "kg") return n;
  if (unit === "pièce") return n * (avgKg || 0);
  if (unit === "douzaine") return n * 12 * (avgKg || 0);
  return 0;
}
function lineTotal(product, sel) {
  return totalKg(sel.unit, sel.qty, product.avgKg) * product.price;
}

// Max quantity per product, expressed in the selected unit. The plafond is
// stored in kg on each product (maxKg) and converted to whichever unit the
// user picked. « Vente directe particuliers », pas de gros : on bloque à ce
// plafond et on affiche un message clair.
function maxQtyForUnit(unit, maxKg, avgKg) {
  if (!maxKg) return Infinity;
  if (unit === "kg")       return maxKg;
  if (unit === "g")        return maxKg * 1000;
  if (unit === "pièce")    return avgKg > 0 ? Math.floor(maxKg / avgKg) : 999;
  if (unit === "douzaine") return avgKg > 0 ? Math.floor(maxKg / (avgKg * 12)) : 99;
  return Infinity;
}
function formatMaxLabel(unit, max) {
  if (unit === "g")        return `${max} g max.`;
  if (unit === "kg")       return `${max} kg max.`;
  if (unit === "pièce")    return `${max} pièces max.`;
  if (unit === "douzaine") return `${max} douzaines max.`;
  return "";
}

// Date helpers ──────────────────────────────────────────────
// User rules:
//  · Products arrive Thursday → from Thu morning, this week's Fri/Sat/Sun become bookable.
//  · Wednesday market is leftovers from Sunday → from Sun, the upcoming Wed becomes bookable.
//  · Last accepted order: same-day before 8:00 (le matin même, avant 8h).
// In test mode, gating is disabled so all upcoming slots show up.
function availableDates(placeId, opts = {}) {
  const { testMode = false } = opts;
  const dayMap = { "Mer.": 3, "Ven.": 5, "Sam.": 6, "Dim.": 0 };
  const place = PLACES.find((p) => p.id === placeId);
  if (!place) return [];
  const targets = (place.days || []).map((d) => dayMap[d]);

  const now = new Date();
  const today = now.getDay();
  const today0 = new Date(now);today0.setHours(0, 0, 0, 0);

  const result = [];
  for (const target of targets) {
    // In test mode, expose the next 4 occurrences of each day.
    const occurrences = testMode ? 4 : 1;
    for (let k = 0; k < occurrences; k++) {
      let daysAhead = (target - today + 7) % 7;
      // Si c'est aujourd'hui : on autorise la prise de commande tant qu'il est
      // avant 8h. Passé 8h, on saute à la prochaine semaine.
      if (daysAhead === 0) {
        if (testMode || now.getHours() >= 8) daysAhead = 7;
      }
      daysAhead += k * 7;

      const d = new Date(today0);
      d.setDate(d.getDate() + daysAhead);

      if (!testMode) {
        // Refresh-window gate (Wed pickup → Sun before; Fri/Sat/Sun → Thu before)
        const refreshDay = target === 3 ? 0 : 4;
        const back = (target - refreshDay + 7) % 7 || 7;
        const refresh = new Date(d);
        refresh.setDate(d.getDate() - back);
        refresh.setHours(0, 0, 0, 0);
        if (today0 < refresh) continue;

        // Cutoff : le matin même avant 8h. Passé cette heure, le créneau du
        // jour disparait de la liste des dispos.
        const cutoff = new Date(d);
        cutoff.setHours(8, 0, 0, 0);
        if (now > cutoff) continue;
      }

      // Saint-Vivien market is closed in January (still enforced in test mode for realism)
      if (placeId === "saintvivien" && d.getMonth() === 0) continue;

      result.push(d);
    }
  }
  result.sort((a, b) => a - b);
  return result;
}
function fmtDate(d) {
  const days = ["Dim.", "Lun.", "Mar.", "Mer.", "Jeu.", "Ven.", "Sam."];
  const months = ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."];
  return `${days[d.getDay()]} ${d.getDate()} ${months[d.getMonth()]}`;
}

// ─────────────────────────────────────────────────────────────────────────────
// Step indicator
// ─────────────────────────────────────────────────────────────────────────────
function StepIndicator({ step, labels, onJump }) {
  return (
    <ol className="flex items-stretch rounded-card overflow-hidden bg-white shadow-soft">
      {labels.map((l, i) => {
        const n = i + 1;
        const done = n < step;
        const active = n === step;
        return (
          <li key={l} className="flex-1 min-w-0">
            <button
              disabled={n > step}
              onClick={() => done && onJump(n)}
              className={
              "w-full h-full px-2 md:px-5 py-3 md:py-4 flex flex-col md:flex-row items-center justify-center gap-1.5 md:gap-3 text-center md:text-left transition-colors " + (
              active ? "bg-foam" : done ? "hover:bg-foam/50" : "opacity-50 cursor-not-allowed")
              }>
              
              <span className={"shrink-0 w-7 h-7 grid place-items-center rounded-full text-[13px] font-semibold " + (
              active ? "bg-primary text-white" : done ? "bg-success text-white" : "bg-foam text-mute")}>
                {done ? <Icon name="check" size={14} /> : n}
              </span>
              <span className={"text-[11px] md:text-[14px] font-semibold leading-tight text-balance " + (
              active ? "text-primary" : done ? "text-success" : "text-mute")}>{l}</span>
            </button>
          </li>);

      })}
    </ol>);

}

// ─────────────────────────────────────────────────────────────────────────────
// Step 1
// ─────────────────────────────────────────────────────────────────────────────
function Step1({ place, date, setPlace, setDate, onNext, testMode, onToggleTest }) {
  const dates = React.useMemo(() => availableDates(place.id, { testMode }), [place.id, testMode]);
  const anyAvailable = React.useMemo(
    () => PLACES.some((p) => availableDates(p.id, { testMode }).length > 0),
    [testMode]
  );
  return (
    <div>
      <div className="flex items-center justify-between gap-3 flex-wrap mb-1">
        <div className="eyebrow">Étape 1 sur 3</div>
        <label className="flex items-center gap-2 cursor-pointer select-none text-mute hover:text-primary transition-colors">
          <span className="text-[12px] uppercase tracking-eyebrow font-semibold">Mode test</span>
          <span className={"relative w-9 h-5 rounded-full transition-colors " + (testMode ? "bg-primary" : "bg-line")}>
            <span className={"absolute top-0.5 w-4 h-4 rounded-full bg-white shadow-sm transition-all " + (testMode ? "left-[18px]" : "left-0.5")} />
          </span>
          <input type="checkbox" className="sr-only" checked={testMode} onChange={(e) => onToggleTest(e.target.checked)} aria-label="Activer le mode test" />
        </label>
      </div>
      <h2 className="font-serif text-primary text-[26px] md:text-[32px] leading-tight">Où et quand souhaitez-vous récupérer&nbsp;?</h2>
      <p className="body mt-2">Sélectionnez votre point de retrait, puis choisissez une date parmi les créneaux disponibles cette semaine.</p>
      {testMode &&
      <div className="mt-4 rounded-card bg-warning/10 px-4 py-2.5 text-[13px] text-[#92400E] flex items-center gap-2.5 leading-snug">
          <Icon name="alert-triangle" size={16} />
          <span><strong>Mode test activé&nbsp;:</strong> les fenêtres d'ouverture hebdomadaires sont ignorées, toutes les prochaines dates sont sélectionnables. Aucune commande réelle ne sera passée.</span>
        </div>
      }

      <div className="mt-7 grid md:grid-cols-3 gap-3 md:gap-4">
        {PLACES.map((p) => {
          const active = place.id === p.id;
          const placeDates = availableDates(p.id, { testMode });
          const unavailable = placeDates.length === 0;
          return (
            <button
              key={p.id}
              onClick={() => {setPlace(p);setDate(null);}}
              className={"text-left p-5 rounded-card transition-all " + (
              active ?
              "bg-foam shadow-marine" :
              unavailable ?
              "bg-foam/30 hover:bg-foam/50" :
              "bg-white shadow-soft hover:shadow-card")}>
              
              <div className="flex items-center gap-2 eyebrow mb-2">
                <PlaceIcon kind={p.icon} size={18} />{p.kind}
              </div>
              <div className={"font-serif text-[20px] leading-tight " + (unavailable && !active ? "text-mute" : "text-primary")}>{p.name}</div>
              <p className="text-mute text-[13px] mt-2 leading-[1.45]">{p.address}</p>
              <div className="mt-3 flex gap-1.5 flex-wrap">
                {p.days.map((d) =>
                <span key={d} className={"text-[11px] font-semibold px-2 py-1 rounded-full " + (active ? "bg-primary text-white" : "bg-foam text-primary")}>{d}</span>
                )}
                {unavailable && <span className="text-[11px] font-semibold px-2 py-1 rounded-full bg-white text-mute">Indisponible cette semaine</span>}
              </div>
            </button>);

        })}
      </div>

      <div className="mt-8">
        <div className="text-[14px] font-semibold text-ink mb-3 flex items-center gap-2">
          <Icon name="calendar" size={16} className="text-primary" />
          Choisissez une date de retrait
        </div>
        {dates.length > 0 ?
        <div className="grid grid-cols-2 md:grid-cols-4 gap-2.5">
            {dates.map((d) => {
            const key = d.toISOString();
            const sel = date && new Date(date).toDateString() === d.toDateString();
            return (
              <button
                key={key}
                onClick={() => setDate(d.toISOString())}
                className={"px-3 py-3.5 rounded-btn text-[15px] font-medium transition-all " + (
                sel ? "bg-foam text-primary shadow-marine" : "bg-white text-ink shadow-soft hover:shadow-card")}>
                
                  {fmtDate(d)}
                </button>);

          })}
          </div> :

        <div className="rounded-card bg-foam/60 p-4 text-[14px] text-ink/80 leading-[1.5]">
            <strong className="text-primary">Aucun créneau disponible pour ce lieu cette semaine.</strong>
            {anyAvailable ?
          " Essayez un autre point de retrait, ou revenez en début de la prochaine fenêtre d'ouverture." :
          <> Nos créneaux s'ouvrent le <strong>jeudi</strong> (boutique vendredi/samedi et marché du dimanche) et le <strong>dimanche</strong> (marché du mercredi).</>}
          </div>
        }
        <p className="text-mute text-[12px] mt-3 italic leading-snug">
          Nous recevons les produits le jeudi&nbsp;: les dispos vendredi/samedi/dimanche s'ouvrent à partir du jeudi. Le marché du mercredi est ouvert à la commande à partir du dimanche précédent. <strong className="not-italic text-ink/80">Dernier délai&nbsp;: le matin même du retrait, avant 8h.</strong>
        </p>
      </div>

      <div className="mt-7">
        <InfoBox title="À savoir avant de commander">
          <ul className="space-y-1 list-disc pl-5">
            <li>Vous pouvez commander <strong>jusqu'au matin même du retrait, avant 8h</strong>.</li>
            <li>Tous les produits ne sont pas disponibles chaque semaine. Seuls ceux disponibles s'affichent.</li>
            <li><strong>Le total affiché est une estimation.</strong> Les tarifs au kilo peuvent varier selon la saison et l'arrivage&nbsp;; le montant définitif est confirmé au retrait.</li>
            <li>Aucun paiement en ligne&nbsp;: vous réglez au retrait (espèces, chèque ou CB selon le lieu).</li>
          </ul>
        </InfoBox>
      </div>

      <div className="mt-8 flex items-center justify-end gap-3">
        <Button variant="primary" size="lg" iconRight="arrow-right" disabled={!date} onClick={onNext}>Continuer</Button>
      </div>
    </div>);

}

// ─────────────────────────────────────────────────────────────────────────────
// Step 2 — Product picker
// ─────────────────────────────────────────────────────────────────────────────
function QtyInput({ unit, value, max, onChange }) {
  const step = unitStep(unit);
  const min = unitMin(unit);
  const cappedMax = Number.isFinite(max) ? max : undefined;
  const clamp = (n) => {
    if (cappedMax !== undefined && n > cappedMax) return cappedMax;
    return n;
  };
  return (
    <input
      type="number"
      value={value}
      step={step}
      min={min}
      max={cappedMax}
      onChange={(e) => {
        const v = e.target.value;
        if (v === "") return onChange("");
        const n = parseFloat(v);
        if (!isNaN(n)) onChange(clamp(n));
      }}
      onBlur={(e) => {
        const n = parseFloat(e.target.value);
        if (isNaN(n) || n < min) onChange(min);
        else if (cappedMax !== undefined && n > cappedMax) onChange(cappedMax);
      }}
      className="w-20 h-10 px-2 rounded-btn bg-foam/70 text-[15px] font-semibold tabular-nums text-ink text-center outline-none transition-shadow focus:bg-white focus:ring-2 focus:ring-primary/30"
      inputMode="decimal"
      aria-label={"Quantité (" + unit + ")"} />);


}

function ProductRow({ product, selection, onToggle, onQty, onUnit, onFormat, onInfo }) {
  const active = !!selection;
  const isOyster = product.icon === "oyster";
  const maxQ = active ? maxQtyForUnit(selection.unit, product.maxKg, product.avgKg) : Infinity;
  const atMax = active && Number.isFinite(maxQ) && Number(selection.qty) >= maxQ;
  return (
    <div
      data-product-id={product.id}
      data-sheet-id={product.sheetId || ""}
      className={"p-3 md:p-4 rounded-card transition-colors " + (
      active ? "bg-foam shadow-soft" : "bg-white shadow-soft hover:shadow-card")}>
      <div className="flex flex-wrap items-center gap-3 md:gap-4 md:flex-nowrap">
        <input
          type="checkbox"
          checked={active}
          onChange={() => onToggle(product)}
          className="w-5 h-5 accent-primary cursor-pointer shrink-0"
          aria-label={"Sélectionner " + product.name} />
        
        <div className={"w-11 h-11 md:w-12 md:h-12 rounded-card grid place-items-center shrink-0 transition-colors " + (
        active ? "bg-primary/10 text-primary" : "bg-foam text-primary")}>
          <ProductImg name={product.icon} size={26} />
        </div>
        <div className="flex-1 min-w-0">
          <div className="font-semibold text-ink text-[15px] leading-snug">
            {product.name}
            {product.subtitle && <span className="text-mute font-normal italic">, {product.subtitle}</span>}
          </div>
          <div className="text-mute text-[13px] leading-snug mt-0.5">{product.hint} · <span className="text-primary font-medium whitespace-nowrap">{product.price}&nbsp;€/kg</span></div>
        </div>
        <button onClick={() => onInfo(product)} aria-label="Plus d'infos" className="grid w-9 h-9 place-items-center rounded-full bg-foam text-primary hover:bg-primary hover:text-white transition-colors shrink-0"><Icon name="help-circle" size={16} /></button>
        {active &&
        <div className="flex items-center gap-2 w-full md:w-auto md:shrink-0 pl-[56px] md:pl-0">
            <QtyInput unit={selection.unit} value={selection.qty} max={maxQ} onChange={(v) => onQty(product.id, v)} />
            <select
            value={selection.unit}
            onChange={(e) => onUnit(product.id, e.target.value)}
            className="h-10 px-2.5 rounded-btn bg-foam/70 text-[14px] text-ink w-auto min-w-[92px] max-w-[150px] md:w-[112px] outline-none focus:bg-white focus:ring-2 focus:ring-primary/30">
            
              {product.units.map((u) => <option key={u} value={u}>{pluralize(u, selection.qty)}</option>)}
            </select>
          </div>
        }
      </div>

      {/* Conditionnement (huîtres uniquement) */}
      {active && isOyster &&
      <div className="mt-3 pt-3 flex items-center gap-3 flex-wrap pl-[60px] md:pl-[68px]">
          <span className="text-mute text-[13px] font-medium">Conditionnement&nbsp;:</span>
          <div className="inline-flex rounded-btn bg-foam overflow-hidden">
            {[
          { id: "poche", label: "Poche", hint: "Filet souple" },
          { id: "bourriche", label: "Bourriche", hint: "Cagette en bois traditionnelle" }].
          map((opt) => {
            const sel = (selection.format || "poche") === opt.id;
            return (
              <button
                key={opt.id}
                type="button"
                title={opt.hint}
                onClick={() => onFormat(product.id, opt.id)}
                className={"px-3 md:px-4 h-8 text-[13px] font-semibold transition-colors " + (
                sel ? "bg-primary text-white" : "text-primary hover:bg-foam")}>
                
                  {opt.label}
                </button>);

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

      {/* Note synthétique au pied de la ligne (ex. : explication de la « chambre » sur les déclassées) */}
      {active && product.footnote &&
        <p className="mt-3 pl-[60px] md:pl-[68px] text-[12px] text-mute italic leading-[1.55] text-wrap-pretty">
          {product.footnote}
        </p>
      }
    </div>);

}

function Step2({ selected, setSelected, onPrev, onNext, openInfo, availability }) {
  const toggle = (p) => {
    setSelected((s) => {
      const n = { ...s };
      if (n[p.id]) delete n[p.id];else
      {
        const sel = { qty: unitDefault(p.units[0]), unit: p.units[0] };
        if (p.icon === "oyster") sel.format = "poche";
        n[p.id] = sel;
      }
      return n;
    });
  };
  const setQty = (id, qty) => setSelected((s) => {
    const cur = s[id];if (!cur) return s;
    return { ...s, [id]: { ...cur, qty } };
  });
  const setUnit = (id, unit) => setSelected((s) => {
    const cur = s[id];if (!cur) return s;
    // Reset qty to a sensible default for the new unit
    return { ...s, [id]: { ...cur, unit, qty: unitDefault(unit) } };
  });
  const setFormat = (id, format) => setSelected((s) => {
    const cur = s[id];if (!cur) return s;
    return { ...s, [id]: { ...cur, format } };
  });

  const count = Object.keys(selected).length;

  return (
    <div>
      <div className="mb-1 eyebrow">Étape 2 sur 3</div>
      <div className="flex flex-wrap items-baseline justify-between gap-3">
        <h2 className="font-serif text-primary text-[26px] md:text-[32px] leading-tight">Que souhaitez-vous commander&nbsp;?</h2>
        <span className="text-mute text-[14px]">{count} produit{count > 1 ? "s" : ""} sélectionné{count > 1 ? "s" : ""}</span>
      </div>
      <p className="body mt-2">Cochez les produits qui vous intéressent, puis indiquez la quantité et l'unité. Tous nos prix sont indiqués <strong>au kilo</strong> et restent <strong>indicatifs</strong>&nbsp;: le total est une estimation, susceptible de varier selon la saison et l'arrivage.</p>

      <div className="mt-8 space-y-8">
        {Object.entries(PRODUCTS).map(([cat, items]) => {
          const visibleItems = items.filter((p) => isAvailable(p, availability));
          if (visibleItems.length === 0) return null;
          return (
            <section key={cat}>
              <h3 className="eyebrow !text-primary !text-[13px] mb-3">{cat}</h3>
              <div className="space-y-2">
                {visibleItems.map((p) =>
                  <ProductRow key={p.id} product={p} selection={selected[p.id]} onToggle={toggle} onQty={setQty} onUnit={setUnit} onFormat={setFormat} onInfo={openInfo} />
                )}
              </div>
            </section>
          );
        })}
      </div>

      <div className="mt-8 flex items-center justify-between gap-3">
        <Button variant="tertiary" iconLeft="chevron-left" onClick={onPrev}>Retour</Button>
        <Button variant="primary" size="lg" iconRight="arrow-right" disabled={count === 0} onClick={onNext}>Continuer</Button>
      </div>
    </div>);

}

// ─────────────────────────────────────────────────────────────────────────────
// Step 3 — Contact + recap
// ─────────────────────────────────────────────────────────────────────────────
function Step3({ place, date, selected, form, setForm, captchaOk, setCaptchaOk, onPrev, onSubmit, onJump }) {
  const total = Object.entries(selected).reduce((sum, [id, sel]) => {
    const p = findProduct(id);if (!p) return sum;
    return sum + lineTotal(p, sel);
  }, 0);

  const valid = form.consent && form.prenom && form.nom && form.email && form.tel && captchaOk;

  return (
    <div>
      <div className="mb-1 eyebrow">Étape 3 sur 3</div>
      <h2 className="font-serif text-primary text-[26px] md:text-[32px] leading-tight">Vos coordonnées</h2>
      <p className="body mt-2">Nous vous confirmons la disponibilité par email sous 24&nbsp;h.</p>

      <div className="mt-7 grid md:grid-cols-2 gap-4">
        <Field label="Prénom" required>
          <Input value={form.prenom} onChange={(e) => setForm({ ...form, prenom: e.target.value })} required />
        </Field>
        <Field label="Nom" required>
          <Input value={form.nom} onChange={(e) => setForm({ ...form, nom: e.target.value })} required />
        </Field>
        <Field label="Téléphone" required hint="Pour vous prévenir le jour du retrait.">
          <Input type="tel" value={form.tel} onChange={(e) => setForm({ ...form, tel: e.target.value })} required />
        </Field>
        <Field label="Email" required>
          <Input type="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} required />
        </Field>
        <Field label="Commentaire libre" full hint="Allergie, demande particulière, etc.">
          <Textarea value={form.message} onChange={(e) => setForm({ ...form, message: e.target.value })} />
        </Field>
      </div>

      <div className="mt-5 space-y-3">
        <Checkbox checked={form.consent} onChange={(e) => setForm({ ...form, consent: e.target.checked })}>
          J'accepte que mes données soient utilisées pour traiter ma commande. <span className="text-danger">*</span>
        </Checkbox>
      </div>

      {/* Anti-bot */}
      <div className="mt-5">
        <Captcha onValidChange={setCaptchaOk} />
      </div>

      {/* Récap */}
      <div className="mt-8 panel p-6 md:p-7 bg-foam">
        <div className="flex items-center justify-between mb-4">
          <h3 className="font-serif text-primary text-[22px]">Récapitulatif</h3>
          <button onClick={() => onJump(1)} className="text-primary text-[13px] font-semibold underline underline-offset-4">Modifier</button>
        </div>
        <div className="space-y-3 text-[15px]">
          <div className="flex justify-between gap-4 pb-3">
            <span className="text-mute">Retrait</span>
            <span className="text-ink text-right font-medium">{place.name}<br /><span className="text-mute font-normal">{date ? fmtDate(new Date(date)) : "À choisir"}</span></span>
          </div>
          <div className="flex items-center justify-between pb-2">
            <span className="text-mute">Produits</span>
            <button onClick={() => onJump(2)} className="text-primary text-[13px] font-semibold underline underline-offset-4">Modifier</button>
          </div>
          {Object.entries(selected).map(([id, sel]) => {
            const p = findProduct(id);if (!p) return null;
            const lt = lineTotal(p, sel);
            return (
              <div key={id} className="grid grid-cols-[1fr_auto_auto] gap-3 items-baseline text-[14px] text-ink">
                <span className="truncate">
                  {p.name}
                  {sel.format && <span className="text-mute font-normal italic"> · {sel.format === "poche" ? "Poche" : "Bourriche"}</span>}
                </span>
                <span className="text-mute tabular-nums">{sel.qty} {pluralize(sel.unit, sel.qty)}</span>
                <span className="font-medium tabular-nums text-right min-w-[64px]">{lt.toFixed(2)}&nbsp;€</span>
              </div>);

          })}
          <div className="pt-3 flex items-baseline justify-between">
            <span className="text-mute">Total estimé</span>
            <span className="font-serif text-primary text-[26px] tabular-nums">{total.toFixed(2)}&nbsp;€</span>
          </div>
          <p className="text-mute text-[12px] mt-1">Tarif indicatif basé sur le poids moyen pour les pièces. Le montant exact vous est communiqué au retrait, règlement sur place.</p>
          <p className="text-mute text-[12px] mt-1">Selon les arrivages, certains produits peuvent avoir été congelés puis décongelés. Si vous souhaitez en congeler un, demandez-nous conseil au retrait.</p>
        </div>
      </div>

      <div className="mt-8 flex items-center justify-between gap-3 flex-wrap">
        <Button variant="tertiary" iconLeft="chevron-left" onClick={onPrev}>Retour</Button>
        <div className="flex flex-col items-end gap-1.5">
          <Button variant="primary" size="lg" iconRight="check" disabled={!valid} onClick={onSubmit}>Confirmer ma commande</Button>
          {!valid &&
          <span className="text-mute text-[12px] italic">
              {!form.consent ?
            "Cochez la case d'acceptation pour confirmer" :
            !captchaOk ?
            "Validez la question anti-robot" :
            "Renseignez prénom, nom, téléphone et email"}
            </span>
          }
        </div>
      </div>
    </div>);

}

// ─────────────────────────────────────────────────────────────────────────────
// Confirm & InfoPopup
// ─────────────────────────────────────────────────────────────────────────────
function buildOrderPDF({ orderRef, place, date, selected, form, total }) {
  const jsPDFCtor = window.jspdf && window.jspdf.jsPDF || window.jsPDF;
  if (!jsPDFCtor) {
    alert("Le générateur PDF n'est pas encore chargé. Réessayez dans un instant.");
    return null;
  }
  const doc = new jsPDFCtor({ unit: "mm", format: "a4" });
  const PRIMARY = [3, 75, 134]; // #034B86
  const PRIMARY_DARK = [2, 47, 85]; // #022F55
  const SAND = [232, 220, 196];
  const FOAM = [244, 240, 232];
  const INK = [31, 41, 55];
  const MUTE = [107, 114, 128];
  const LINE = [229, 231, 235];

  // ── Header band
  doc.setFillColor(...PRIMARY);
  doc.rect(0, 0, 210, 32, "F");
  doc.setTextColor(255, 255, 255);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(18);
  doc.text("Huîtres Gigas Meduli", 16, 16);
  doc.setFont("helvetica", "normal");
  doc.setFontSize(10);
  doc.text("Ferme Eau Médoc · Saint-Vivien-de-Médoc", 16, 22);
  doc.setFontSize(9);
  doc.text(decodeEmail() + " · 05 56 09 58 32", 16, 27);

  // Reference badge (top right)
  doc.setFillColor(...SAND);
  doc.rect(140, 8, 58, 18, "F");
  doc.setTextColor(...PRIMARY_DARK);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(8);
  doc.text("RÉFÉRENCE", 144, 14);
  doc.setFontSize(11);
  doc.text(String(orderRef), 144, 21);

  // ── Title
  let y = 44;
  doc.setTextColor(...PRIMARY);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(20);
  doc.text("Récapitulatif de votre commande", 16, y);
  y += 7;
  doc.setTextColor(...MUTE);
  doc.setFont("helvetica", "normal");
  doc.setFontSize(10);
  doc.text("Date de commande : " + new Date().toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" }), 16, y);
  y += 10;

  // ── Pickup box
  doc.setFillColor(...FOAM);
  doc.rect(16, y, 178, 24, "F");
  doc.setTextColor(...PRIMARY);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(9);
  doc.text("RETRAIT PRÉVU", 20, y + 6);
  doc.setFontSize(12);
  doc.text(place.name, 20, y + 13);
  doc.setFont("helvetica", "normal");
  doc.setTextColor(...INK);
  doc.setFontSize(10);
  doc.text(place.address, 20, y + 19);
  if (date) {
    const d = new Date(date);
    const dStr = d.toLocaleDateString("fr-FR", { weekday: "long", day: "numeric", month: "long" });
    doc.setFont("helvetica", "bold");
    doc.setTextColor(...PRIMARY);
    doc.text(dStr.charAt(0).toUpperCase() + dStr.slice(1), 150, y + 13);
  }
  y += 32;

  // ── Customer box
  doc.setDrawColor(...LINE);
  doc.setLineWidth(0.3);
  doc.rect(16, y, 178, 24, "S");
  doc.setTextColor(...PRIMARY);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(9);
  doc.text("VOS COORDONNÉES", 20, y + 6);
  doc.setTextColor(...INK);
  doc.setFontSize(10);
  doc.setFont("helvetica", "normal");
  doc.text(((form.prenom || "") + " " + (form.nom || "")).trim() || "Non renseigné", 20, y + 13);
  doc.text("Téléphone : " + (form.tel || "Non renseigné"), 20, y + 19);
  doc.text("Email : " + (form.email || "Non renseigné"), 110, y + 19);
  y += 32;

  // ── Products table
  doc.setTextColor(...PRIMARY);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(12);
  doc.text("Vos produits", 16, y);
  y += 5;

  doc.setFillColor(...FOAM);
  doc.rect(16, y, 178, 8, "F");
  doc.setFontSize(9);
  doc.setTextColor(...PRIMARY);
  doc.text("PRODUIT", 20, y + 5);
  doc.text("QUANTITÉ", 122, y + 5);
  doc.text("PRIX/KG", 150, y + 5);
  doc.text("TOTAL", 178, y + 5);
  y += 8;

  doc.setFont("helvetica", "normal");
  doc.setTextColor(...INK);
  doc.setFontSize(10);

  const entries = Object.entries(selected);
  for (const [id, sel] of entries) {
    const p = findProduct(id);if (!p) continue;
    if (y > 260) {
      doc.addPage();
      y = 20;
    }
    const name = p.name + (p.subtitle ? " (" + p.subtitle + ")" : "") + (sel.format ? " · " + (sel.format === "poche" ? "Poche" : "Bourriche") : "");
    const qtyStr = sel.qty + " " + pluralize(sel.unit, sel.qty);
    const lineT = lineTotal(p, sel);
    doc.text(doc.splitTextToSize(name, 95), 20, y + 5);
    doc.text(qtyStr, 122, y + 5);
    doc.text(p.price + " €", 150, y + 5);
    doc.setFont("helvetica", "bold");
    doc.text(lineT.toFixed(2) + " €", 178, y + 5);
    doc.setFont("helvetica", "normal");
    doc.setDrawColor(...LINE);
    doc.line(16, y + 8, 194, y + 8);
    y += 10;
  }

  // ── Total
  y += 2;
  doc.setFillColor(...PRIMARY);
  doc.rect(120, y, 74, 14, "F");
  doc.setTextColor(255, 255, 255);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(11);
  doc.text("TOTAL ESTIMÉ", 124, y + 6);
  doc.setFontSize(15);
  doc.text(total.toFixed(2) + " €", 190, y + 10, { align: "right" });
  y += 22;

  // ── Notes
  if (form.message) {
    doc.setTextColor(...PRIMARY);
    doc.setFont("helvetica", "bold");
    doc.setFontSize(10);
    doc.text("Message libre", 16, y);
    y += 5;
    doc.setTextColor(...INK);
    doc.setFont("helvetica", "normal");
    doc.setFontSize(10);
    const lines = doc.splitTextToSize(form.message, 178);
    doc.text(lines, 16, y);
    y += lines.length * 5 + 6;
  }

  // ── Footer disclaimer
  doc.setFillColor(...FOAM);
  doc.rect(16, y, 178, 26, "F");
  doc.setTextColor(...PRIMARY_DARK);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(9);
  doc.text("À savoir", 20, y + 6);
  doc.setFont("helvetica", "normal");
  doc.setTextColor(...INK);
  doc.setFontSize(9);
  doc.text("· Le total ci-dessus est une estimation. Les tarifs au kilo peuvent varier selon la saison.", 20, y + 12);
  doc.text("· Aucun paiement en ligne : règlement sur place au retrait (espèces, chèque ou CB).", 20, y + 17);
  doc.text("· Vous pouvez commander jusqu'au matin même du retrait, avant 8h.", 20, y + 22);

  // Bottom margin
  doc.setTextColor(...MUTE);
  doc.setFontSize(8);
  doc.text("Ferme Eau Médoc · 1 La Petite Canau · 33590 Saint-Vivien-de-Médoc · huitresgigasmeduli.com", 105, 290, { align: "center" });

  return doc;
}

function ConfirmScreen({ orderRef, place, date, selected, form, onNav }) {
  const total = React.useMemo(
    () => Object.entries(selected).reduce((sum, [id, sel]) => {
      const p = findProduct(id);if (!p) return sum;
      return sum + lineTotal(p, sel);
    }, 0),
    [selected]
  );

  const downloadPDF = () => {
    const doc = buildOrderPDF({ orderRef, place, date, selected, form, total });
    if (doc) doc.save(orderRef + ".pdf");
  };

  return (
    <div className="max-w-[680px] mx-auto">
      <div className="text-center">
        <div className="w-20 h-20 rounded-full bg-success text-white grid place-items-center mx-auto mb-6 text-3xl"><Icon name="check" size={36} /></div>
        <h2 className="font-serif text-primary text-[32px] md:text-[44px] leading-tight">Votre commande a bien été enregistrée !</h2>
        <p className="lead mt-5 text-mute">Numéro de référence&nbsp;: <span className="font-mono font-semibold text-primary">{orderRef}</span></p>
        <p className="body mt-4">Un récapitulatif vient d'être envoyé à <strong>{form.email || "votre adresse email"}</strong>. Nous vous confirmons la disponibilité <strong>sous 24&nbsp;h</strong>.</p>
        <p className="body mt-2 text-mute">Retrait prévu&nbsp;: <strong className="text-ink">{place.name}</strong> · {date && fmtDate(new Date(date))}.</p>
      </div>

      <div className="mt-7 panel p-5 md:p-6 bg-white text-left">
        <div className="flex items-center justify-between gap-4 pb-3">
          <div className="eyebrow">Récapitulatif</div>
          <Button variant="primary" iconLeft="arrow-down" onClick={downloadPDF}>Télécharger le PDF</Button>
        </div>
        <dl className="mt-4 space-y-2 text-[14px]">
          {Object.entries(selected).map(([id, sel]) => {
            const p = findProduct(id);if (!p) return null;
            const lineT = lineTotal(p, sel);
            return (
              <div key={id} className="flex items-baseline justify-between gap-3">
                <dt className="text-ink">
                  {p.name}{p.subtitle && <span className="text-mute italic font-normal">, {p.subtitle}</span>}
                  {sel.format && <span className="text-mute italic font-normal"> · {sel.format === "poche" ? "Poche" : "Bourriche"}</span>}
                  <span className="text-mute"> · {sel.qty} {pluralize(sel.unit, sel.qty)}</span>
                </dt>
                <dd className="text-primary font-semibold tabular-nums">{lineT.toFixed(2)}&nbsp;€</dd>
              </div>);

          })}
        </dl>
        <div className="flex items-baseline justify-between gap-4 mt-4 pt-4">
          <span className="font-serif text-primary text-[18px]">Total estimé</span>
          <span className="font-serif text-primary text-[26px] tabular-nums">{total.toFixed(2)}&nbsp;€</span>
        </div>
        <p className="text-mute text-[12px] mt-2 italic leading-snug">Estimation indicative. Le montant final dépend du poids exact et des arrivages&nbsp;; il vous est communiqué au retrait.</p>
      </div>

      <div className="mt-6 panel p-5 bg-foam">
        <div className="eyebrow mb-2">Pour toute question</div>
        <ul className="space-y-1.5 text-[15px]">
          <li><ProtectedEmail withIcon iconSize={15} className="text-primary flex items-center gap-2" /></li>
          <li className="flex items-center gap-2"><Icon name="phone" size={15} className="text-primary" /><a href="tel:0556095832" className="text-primary">05 56 09 58 32</a></li>
        </ul>
      </div>

      <div className="mt-8 flex justify-center gap-3 flex-wrap">
        <Button variant="primary" onClick={() => onNav("home")}>Retour à l'accueil</Button>
        <Button variant="tertiary" onClick={() => onNav("ou-trouver")}>Préparer mon trajet</Button>
      </div>
    </div>);

}

function InfoPopup({ product, onClose }) {
  if (!product) return null;
  return (
    <div className="fixed inset-0 z-50 grid place-items-center p-4 bg-black/50" onClick={onClose}>
      <div className="bg-white rounded-hero max-w-[480px] w-full p-6 md:p-7 shadow-marine-lg relative" onClick={(e) => e.stopPropagation()}>
        <button className="absolute top-4 right-4 w-9 h-9 grid place-items-center rounded-full hover:bg-foam text-ink" onClick={onClose} aria-label="Fermer"><Icon name="x" size={18} /></button>
        <div className="w-16 h-16 rounded-card bg-foam grid place-items-center mb-4"><ProductImg name={product.icon} size={40} /></div>
        <h3 className="font-serif text-primary text-[24px] leading-tight">{product.name}</h3>
        {product.subtitle && <p className="font-serif italic text-primary-light text-[18px] mt-1">{product.subtitle}</p>}
        <p className="text-ink/85 text-[15px] mt-3">{product.hint}</p>
        {product.cuisson &&
        <div className="mt-4 rounded-card bg-foam/80 p-3.5 text-[14px] text-ink/90 leading-[1.55]">
            <div className="font-semibold text-primary mb-1 flex items-center gap-2"><Icon name="utensils" size={15} />Notre court-bouillon parfumé</div>
            Laurier, herbes de Provence, anis étoilé, sel, plus un soupçon de <strong>piment en poudre</strong> et de <strong>piments oiseaux</strong>. Un court-bouillon qui relève délicatement les saveurs marines sans piquer.
          </div>
        }
        <dl className="mt-5 grid grid-cols-2 gap-3 text-[14px]">
          <div><dt className="text-mute text-[12px] uppercase tracking-eyebrow">Tarif</dt><dd className="font-medium text-ink">{product.price}&nbsp;€/kg</dd></div>
          <div><dt className="text-mute text-[12px] uppercase tracking-eyebrow">Unité(s)</dt><dd className="font-medium text-ink">{product.units.join(", ")}</dd></div>
        </dl>
        <p className="text-[12px] text-mute mt-5 italic">Tarif indicatif au kilo. Le prix final est une <strong>estimation</strong> et peut varier selon la saison et l'arrivage.</p>
      </div>
    </div>);

}

// ─────────────────────────────────────────────────────────────────────────────
// Local persistence — survive accidental tab closes
// ─────────────────────────────────────────────────────────────────────────────
const ORDER_STORAGE_KEY = "eaumedoc.order.v1";
function loadOrderDraft() {
  try {
    const raw = localStorage.getItem(ORDER_STORAGE_KEY);
    if (!raw) return null;
    const data = JSON.parse(raw);
    // Stale-protect: drop drafts older than 7 days
    if (!data.savedAt || Date.now() - data.savedAt > 7 * 86400 * 1000) {
      localStorage.removeItem(ORDER_STORAGE_KEY);
      return null;
    }
    return data;
  } catch {return null;}
}
function saveOrderDraft(payload) {
  try {
    localStorage.setItem(ORDER_STORAGE_KEY, JSON.stringify({ ...payload, savedAt: Date.now() }));
  } catch {}
}
function clearOrderDraft() {
  try {localStorage.removeItem(ORDER_STORAGE_KEY);} catch {}
}

// ─────────────────────────────────────────────────────────────────────────────
// Order page
// ─────────────────────────────────────────────────────────────────────────────
function OrderPage({ onNav }) {
  // Hydrate from localStorage once on mount (survive accidental tab closes)
  const draft = React.useMemo(() => loadOrderDraft(), []);

  // Test mode lets the user explore the flow without being gated by the
  // current week's availability window. Persisted in localStorage so it
  // survives reload while the user is testing.
  const [testMode, setTestMode] = React.useState(() => {
    try {return localStorage.getItem("eaumedoc.order.testmode") === "1";} catch {return false;}
  });
  React.useEffect(() => {
    try {localStorage.setItem("eaumedoc.order.testmode", testMode ? "1" : "0");} catch {}
  }, [testMode]);

  const [step, setStep] = React.useState(() => draft?.step || 1);
  const [place, setPlace] = React.useState(() => {
    const fromDraft = draft?.placeId && PLACES.find((p) => p.id === draft.placeId);
    if (fromDraft && availableDates(fromDraft.id, { testMode }).length > 0) return fromDraft;
    const firstAvailable = PLACES.find((p) => availableDates(p.id, { testMode }).length > 0);
    return firstAvailable || PLACES[0];
  });
  const [date, setDate] = React.useState(() => {
    if (!draft?.date || !draft?.placeId) return null;
    // Validate that the saved date is still in the active window
    const valid = availableDates(draft.placeId, { testMode }).some(
      (d) => d.toDateString() === new Date(draft.date).toDateString()
    );
    return valid ? draft.date : null;
  });
  const [selected, setSelected] = React.useState(() => draft?.selected || { n3: { qty: 2, unit: "douzaine" } });
  const [form, setForm] = React.useState(() => ({
    prenom: draft?.form?.prenom || "",
    nom: draft?.form?.nom || "",
    tel: draft?.form?.tel || "",
    email: draft?.form?.email || "",
    message: draft?.form?.message || "",
    // Never restore consent (must be a fresh, intentional opt-in)
    consent: false
  }));
  const [info, setInfo] = React.useState(null);
  const [done, setDone] = React.useState(null);
  const [draftRestored, setDraftRestored] = React.useState(() => !!draft);
  const [captchaOk, setCaptchaOk] = React.useState(false);
  const { available: availabilityMap } = useAvailability();

  // Expose a tiny nav hook so the "Plafond atteint → demandez un devis" link
  // inside the Step 2 product row can reach the parent onNav prop without
  // dragging it through three layers of React props.
  React.useEffect(() => {
    window.__navTo = onNav;
    return () => { if (window.__navTo === onNav) delete window.__navTo; };
  }, [onNav]);

  // Persist on every state change (but not after submit)
  React.useEffect(() => {
    if (done) return;
    saveOrderDraft({
      step,
      placeId: place.id,
      date,
      selected,
      form: { ...form, consent: false } // exclude consent
    });
  }, [step, place, date, selected, form, done]);

  const goStep = (n) => {setStep(n);window.scrollTo({ top: 0, behavior: "smooth" });};
  const submit = () => {
    const ref = "CMD-2026-" + String(Math.floor(Math.random() * 9000) + 1000);
    clearOrderDraft();
    setDone(ref);
    window.scrollTo({ top: 0, behavior: "smooth" });
  };
  const resetDraft = () => {
    clearOrderDraft();
    setStep(1);
    const firstAvailable = PLACES.find((p) => availableDates(p.id, { testMode }).length > 0) || PLACES[0];
    setPlace(firstAvailable);
    setDate(null);
    setSelected({ n3: { qty: 2, unit: "douzaine" } });
    setForm({ prenom: "", nom: "", tel: "", email: "", message: "", consent: false });
    setDraftRestored(false);
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  if (done) {
    return (
      <section className="py-20 md:py-28 bg-foam min-h-[60vh]">
        <div className="container-x">
          <ConfirmScreen orderRef={done} place={place} date={date} selected={selected} form={form} onNav={onNav} />
        </div>
      </section>);

  }

  return (
    <>
      <section className="bg-foam pt-14 md:pt-20 pb-8">
        <div className="container-x max-w-[1000px]">
          <Kicker>Commander en ligne</Kicker>
          <h1 className="h-display mt-3">Passer commande</h1>
          <p className="lead mt-4 max-w-[680px]">Réservez vos produits pour un retrait à notre boutique ou sur l'un de nos marchés. <strong>Aucun paiement en ligne</strong>&nbsp;: règlement directement au retrait.</p>
        </div>
      </section>

      <section className="py-12 md:py-16 bg-foam">
        <div className="container-x max-w-[1000px]">
          {draftRestored &&
          <div className="mb-5 panel p-4 md:p-5 bg-foam flex items-start gap-3 md:items-center md:flex-row flex-col">
              <span className="w-10 h-10 rounded-card bg-primary text-white grid place-items-center shrink-0"><Icon name="check-circle" size={20} /></span>
              <div className="flex-1 text-[14px] md:text-[15px] text-ink leading-snug">
                <strong className="text-primary">Bienvenue&nbsp;!</strong> Nous avons retrouvé votre commande en cours. Vous pouvez la reprendre là où vous l'aviez laissée.
              </div>
              <button onClick={resetDraft} className="text-primary-light hover:text-primary text-[13px] font-semibold underline underline-offset-4 self-start md:self-auto whitespace-nowrap">Repartir de zéro</button>
            </div>
          }
          <StepIndicator
            step={step}
            labels={["Lieu et date", "Vos produits", "Vos coordonnées"]}
            onJump={goStep} />
          
          <div className="panel p-5 md:p-9 mt-5">
            {step === 1 && <Step1 place={place} date={date} setPlace={setPlace} setDate={setDate} onNext={() => goStep(2)} testMode={testMode} onToggleTest={(v) => {setTestMode(v);setDate(null);}} />}
            {step === 2 && <Step2 selected={selected} setSelected={setSelected} onPrev={() => goStep(1)} onNext={() => goStep(3)} openInfo={setInfo} availability={availabilityMap} />}
            {step === 3 && <Step3 place={place} date={date} selected={selected} form={form} setForm={setForm} captchaOk={captchaOk} setCaptchaOk={setCaptchaOk} onPrev={() => goStep(2)} onSubmit={submit} onJump={goStep} />}
          </div>
        </div>
      </section>

      <InfoPopup product={info} onClose={() => setInfo(null)} />
    </>);

}

window.OrderPage = OrderPage;