// FICHE PLANTE — Institut de France style detailed page

// Emphase légère dans les textes éditoriaux : **gras** → <strong>. Le reste du
// texte est rendu tel quel (échappé par React). Sert pour la section Histoire.
function renderEmphasis(text) {
  const parts = String(text == null ? "" : text).split(/\*\*(.+?)\*\*/g);
  return parts.map((seg, i) =>
    i % 2 === 1
      ? <strong key={i} style={{ fontWeight: 600, color: "var(--ink)" }}>{seg}</strong>
      : seg
  );
}

function Fiche({ setRoute, setPlantId, setEditId, setPoleSlug, plantId }) {
  const auth = window.useAuth ? window.useAuth() : { user: null, loading: false };
  // Privilèges dérivés (re-calculés à chaque render — gratuit)
  const role = auth.user?.role || null;
  const canEdit = role === "contributor" || role === "moderator" || role === "admin";
  const canDeleteFiche = role === "moderator" || role === "admin";
  const initial = window.PLANTS.find(pp => pp.id === plantId) || { id: plantId, nom: "—", latin: "—" };
  const [plant, setPlant] = React.useState(initial);
  const [reader, setReader] = React.useState(false);
  const [printOpen, setPrintOpen] = React.useState(false);
  const [lightbox, setLightbox] = React.useState(null);
  const [activeSection, setActiveSection] = React.useState("description");
  const [deleteOpen, setDeleteOpen] = React.useState(false);
  const [deleteSecret, setDeleteSecret] = React.useState("");
  const [deleteError, setDeleteError] = React.useState(null);
  const [deleting, setDeleting] = React.useState(false);
  const [quickPhotoOpen, setQuickPhotoOpen] = React.useState(false);
  const isMobile = useMobile();

  // Fetch unique : fiche complète, photos incluses. Depuis la migration des
  // photos vers Storage (Phase C), les photos sont des URLs légères et /api/get
  // les renvoie directement — plus besoin du second fetch /api/photos.
  React.useEffect(() => {
    fetch(`api/get?id=${encodeURIComponent(plantId)}`, { cache: "no-store" })
      .then(r => r.ok ? r.json() : null)
      .then(full => {
        if (full && !full.error) {
          setPlant(full);
          const idx = window.PLANTS.findIndex(pp => pp.id === plantId);
          if (idx >= 0) window.PLANTS[idx] = { ...window.PLANTS[idx], ...full };
        }
      })
      .catch(() => {});
  }, [plantId]);

  const p = plant;
  const photos = p.photos || {};
  const wiki = p.wikipedia;

  // Toutes les photos disponibles (uploadées + wikipedia), dans l'ordre
  const getSrc = k => k === "wiki" ? (wiki?.thumb || null) : (photos[k] || null);
  const allPhotoKeys = ["p1", "p2", "p3"].filter(k => photos[k]);
  if (wiki?.thumb) allPhotoKeys.push("wiki");
  const validDefault = p.photoDefault && getSrc(p.photoDefault);
  // Photo principale : le choix explicite (photoDefault) prime. Sinon, ordre de
  // préférence = p1 (slot « planche principale ») › wikipedia (planche) › 1re
  // autre locale. Ainsi, ajouter un détail (p2 capitule, p3 feuille…) ne détrône
  // pas la planche principale tant qu'aucune principale n'a été choisie.
  const mainKey = validDefault ? p.photoDefault
    : (photos.p1 ? "p1" : (wiki?.thumb ? "wiki" : (allPhotoKeys[0] || null)));
  const otherKeys = allPhotoKeys.filter(k => k !== mainKey);
  const photoMain    = getSrc(mainKey);
  const photoDetail1 = getSrc(otherKeys[0]) || null;
  const photoDetail2 = getSrc(otherKeys[1]) || null;
  // Crédit d'attribution de la photo principale (licences CC-BY / CC-BY-SA).
  // Stocké dans data.photoCredits = { p1: { text, url }, ... }.
  const photoCredits = p.photoCredits || {};
  const mainCredit = (mainKey && photoCredits[mainKey]) || null;
  // Le bouton "★ Définir principale" sur les vignettes n'est proposé qu'aux
  // utilisateurs connectés avec rôle contributor+. Anonymes : pas d'action.
  const canChooseDefault = allPhotoKeys.length > 1 && canEdit;

  // Liste unifiée de toutes les photos affichées (planche + détails + galerie),
  // dans l'ordre où elles apparaissent — utilisée par la lightbox pour la
  // navigation au clavier et le défilement avec les flèches.
  const lightboxItems = React.useMemo(() => {
    const out = [];
    const seen = new Set();
    const push = (id, label, src) => {
      if (!src || seen.has(id)) return;
      seen.add(id);
      out.push({ id, label, src });
    };
    if (photoMain) push(mainKey || "_main", `${(p.latin || "").toUpperCase()} · PLANCHE PRINCIPALE`, photoMain);
    if (photoDetail1) push(otherKeys[0] || "_d1", "DÉTAIL", photoDetail1);
    if (photoDetail2) push(otherKeys[1] || "_d2", "DÉTAIL", photoDetail2);
    if (Array.isArray(p.galerie)) {
      for (const g of p.galerie) {
        if (photos[g.id]) push(g.id, (g.label || "Photographie").toUpperCase(), photos[g.id]);
      }
    }
    return out;
  }, [photoMain, photoDetail1, photoDetail2, mainKey, otherKeys, p.galerie, photos]);

  const openLightboxFor = (key) => {
    const idx = lightboxItems.findIndex(item => item.id === key);
    if (idx >= 0) setLightbox(idx);
  };

  const handleSetDefault = (key) => {
    (window.authedFetch || fetch)("api/set-default-photo", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ id: p.id, photoDefault: key }),
    })
      .then(r => r.json())
      .then(res => {
        if (res.ok) {
          setPlant(prev => ({ ...prev, photoDefault: key }));
          // Rafraîchit window.PLANTS (et donc la vignette du catalogue) sans
          // recharger la page : refetch /api/list + re-render via setVersion.
          if (window.__reloadPlants) window.__reloadPlants();
        }
      })
      .catch(() => {});
  };

  // Suppression d'une photo depuis la lightbox.
  // - Pour les photos uploadées (p1-p6) : retire la clé du dictionnaire photos.
  // - Pour la photo Wikipedia ("wiki") : envoie wikipedia: null explicite, que
  //   api/save respecte côté serveur (pas de refetch si photo locale présente).
  // Anti-écrasement : récupère fiche + photos complètes avant le save.
  const handleDeletePhoto = async (photoKey) => {
    if (!photoKey) return;
    const isWiki = photoKey === "wiki";
    if (!isWiki && !/^p[1-6]$/.test(photoKey)) return;

    const labels = {
      p1: "Planche principale", p2: "Fleur / capitule", p3: "Feuille — détail",
      p4: "Tige / port", p5: "Habitat", p6: "Vue complémentaire",
      wiki: "Photo Wikipedia",
    };
    const label = labels[photoKey] || photoKey;
    if (!window.confirm(`Supprimer la photo « ${label} » de cette fiche ? Cette action est immédiate.`)) return;

    try {
      const [ficheFull, photosCur] = await Promise.all([
        fetch(`api/get?id=${encodeURIComponent(p.id)}`, { cache: "no-store" }).then(r => r.json()),
        fetch(`api/photos?id=${encodeURIComponent(p.id)}`, { cache: "no-store" }).then(r => r.json()),
      ]);
      if (ficheFull.error) throw new Error(ficheFull.error);

      const nextPhotos = { ...(photosCur && !photosCur.error ? photosCur : {}) };
      delete nextPhotos.error;

      if (isWiki) {
        // Suppression durable de la photo Wikipedia côté serveur
        ficheFull.wikipedia = null;
        // photoDefault ne devrait jamais être "wiki" (logique de Fiche.jsx),
        // mais on sécurise.
        if (ficheFull.photoDefault === "wiki") {
          const remaining = Object.keys(nextPhotos).filter(k => /^p[1-6]$/.test(k));
          ficheFull.photoDefault = remaining[0] || null;
        }
        ficheFull.photos = nextPhotos;
      } else {
        // Suppression d'une photo locale p1-p6
        delete nextPhotos[photoKey];
        ficheFull.photos = nextPhotos;
        // Si la photo supprimée était la photo principale, on bascule sur la
        // première restante (ou null s'il n'y en a plus).
        if (ficheFull.photoDefault === photoKey) {
          const remaining = Object.keys(nextPhotos).filter(k => /^p[1-6]$/.test(k));
          ficheFull.photoDefault = remaining[0] || null;
        }
        // Évite que wikipedia auto réécrase la photo principale du local
        delete ficheFull.wikipedia;
        // Aligne aussi la galerie sur les photos restantes
        if (Array.isArray(ficheFull.galerie)) {
          ficheFull.galerie = ficheFull.galerie.filter(g => g.id !== photoKey);
        }
      }

      const res = await (window.authedFetch || fetch)("api/save", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(ficheFull),
      });
      const json = await res.json();

      // Phase 2 : si HTTP 409, une autre révision pending existe déjà
      if (res.status === 409) {
        window.alert("Une autre proposition est en cours de modération sur cette fiche.\n\n" +
          (json.pendingAuthor ? `Proposée par ${json.pendingAuthor}.\n\n` : "") +
          "Vous pourrez resoumettre une fois la précédente validée ou retirée.");
        setLightbox(null);
        return;
      }
      if (!res.ok || !json.ok) throw new Error(json.error || "Erreur d'enregistrement");

      // Phase 2 : si pending (contributor), la fiche live n'est pas modifiée.
      // On ne met PAS à jour le state local et on affiche un message clair.
      if (json.status === "pending") {
        window.alert("Suppression proposée — votre demande est en attente de modération.\n\n" +
          "La photo reste visible jusqu'à validation par un modérateur. " +
          "Vous pouvez suivre sa progression depuis la page Modération.");
        setLightbox(null);
        return;
      }

      // status === "applied" (moderator+) : comportement historique
      setPlant(prev => ({
        ...prev,
        photos: nextPhotos,
        photoDefault: ficheFull.photoDefault,
        galerie: ficheFull.galerie,
        ...(isWiki ? { wikipedia: null } : {}),
      }));
      setLightbox(null);
    } catch (err) {
      window.alert("Suppression impossible : " + (err.message || "erreur réseau"));
    }
  };

  const hasMorphologie  = !!p.morphologie;
  const hasHabitat      = !!p.habitat || !!p.repartition;
  const hasFloraison    = Array.isArray(p.floraison) && p.floraison.length > 0;
  const hasComestible   = !!(p.comestibilite && p.comestibilite.detail);
  const hasMedicinal    = !!(p.medicinal && p.medicinal.detail);
  const hasBioindic     = !!p.bioindication;
  const hasPrecautions  = !!p.precautions;
  const hasUsages       = hasComestible || hasMedicinal || hasBioindic;
  const hasArbre        = !!p.arbre && typeof p.arbre === "object" && Object.keys(p.arbre).length > 0;
  const hasHistoire     = !!p.histoire;
  const hasSymbolique   = !!p.symbolique;
  const hasRecettes     = Array.isArray(p.recettes) && p.recettes.length > 0;
  const hasNotes        = !!(p.notes && p.notes.trim());
  // Feuille : au moins un caractère morphologique renseigné (forme/marge/nervation/disposition).
  const FEUILLE_CATS    = ["forme", "marge", "nervation", "disposition"];
  const hasFeuille      = !!p.feuille && FEUILLE_CATS.some(c => Array.isArray(p.feuille[c]) && p.feuille[c].length > 0);
  const voirAussiItems  = (Array.isArray(p.voirAussi) ? p.voirAussi : [])
    .map(id => (window.PLANTS || []).find(pp => pp.id === id))
    .filter(Boolean);
  const hasVoirAussi    = voirAussiItems.length > 0;
  // Grappes (pôles thématiques) contenant cette plante — source window.POLES.
  // Sert le bloc « Grappes » : regroupements cliquables vers la page grappe.
  const plantGrappes    = (window.POLES || []).filter(g => Array.isArray(g.plantes) && g.plantes.includes(p.id));
  const hasGrappes      = plantGrappes.length > 0;
  const hasJardins      = Array.isArray(p.jardins) && p.jardins.length > 0;
  const hasGalerie      = Array.isArray(p.galerie) && p.galerie.length > 0;
  const hasContribs     = Array.isArray(p.contributeurs) && p.contributeurs.length > 0;
  const hasIncipit      = !!p.incipit;
  const tags            = Array.isArray(p.usage) ? p.usage : [];
  const goEdit = () => { if (setEditId) setEditId(p.id); setRoute("edit"); };

  const handleDelete = async () => {
    setDeleting(true);
    setDeleteError(null);
    try {
      const res = await (window.authedFetch || fetch)("/api/delete", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ id: p.id, secret: deleteSecret }),
      });
      const data = await res.json();
      if (data.ok) {
        window.PLANTS = window.PLANTS.filter(pp => pp.id !== p.id);
        setRoute("catalogue");
      } else {
        setDeleteError(data.error || "Erreur inconnue");
        setDeleting(false);
      }
    } catch (e) {
      setDeleteError("Erreur réseau");
      setDeleting(false);
    }
  };

  // Pour les fiches arbres minimales (data.arbre présent, mais ni morphologie,
  // ni habitat, ni histoire), on cache les sections vides plutôt que d'afficher
  // un EmptyHint sur des sections jamais destinées à être remplies.
  const showDescription = hasMorphologie || !hasArbre;
  const showHabitat     = hasHabitat || hasFloraison || !hasArbre;
  const showHistoire    = hasHistoire || hasSymbolique || !hasArbre;

  const sections = [
    ...(showDescription ? [{ id: "description", label: "Description" }] : []),
    ...(hasFeuille      ? [{ id: "feuille",     label: "La feuille" }]  : []),
    ...(showHabitat     ? [{ id: "habitat",     label: "Habitat" }]     : []),
    { id: "usages", label: "Usages" },
    ...(hasArbre        ? [{ id: "arbre",       label: "Au jardin" }]   : []),
    ...(showHistoire    ? [{ id: "histoire",    label: "Histoire" }]    : []),
    ...(hasRecettes     ? [{ id: "recettes",    label: "Recettes" }]    : []),
    ...(hasNotes        ? [{ id: "notes",       label: "Notes" }]       : []),
    { id: "jardins", label: "Jardins" },
    ...(hasGalerie      ? [{ id: "galerie",     label: "Galerie" }]     : []),
    ...(hasGrappes      ? [{ id: "grappes",     label: "Grappes" }]     : []),
    ...(hasVoirAussi    ? [{ id: "voir-aussi",  label: "Voir aussi" }]  : []),
    { id: "contribs", label: "Contributeurs" },
  ];

  // Numérotation romaine dynamique : reflète la position effective dans le
  // tableau sections (ordre stable même si des sections sont masquées).
  const ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"];
  const sectionNum = (id) => {
    const idx = sections.findIndex(s => s.id === id);
    return idx >= 0 ? ROMAN[idx] : "";
  };

  React.useEffect(() => {
    const onScroll = () => {
      for (const s of sections) {
        const el = document.getElementById(`fiche-${s.id}`);
        if (el && el.getBoundingClientRect().top < 200) setActiveSection(s.id);
      }
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  const pad = isMobile ? "0 20px" : "0 40px";

  return (
    <main className={`page-enter ${reader ? "reader-mode" : ""}`} style={{ position: "relative" }}>
      {/* TITRE */}
      <section style={{ padding: isMobile ? "28px 0 36px" : "60px 0 100px" }}>
        <div style={{ maxWidth: 1400, margin: "0 auto", padding: pad }}>

          {/* Entête : titre + carte méta */}
          <div style={{ display: "flex", flexDirection: isMobile ? "column" : "row", justifyContent: "space-between", alignItems: isMobile ? "flex-start" : "flex-start", marginBottom: isMobile ? 24 : 60, gap: isMobile ? 20 : 0 }}>
            <div style={{ flex: 1 }}>
              <div className="kicker" style={{ marginBottom: isMobile ? 10 : 16 }}>
                Folio · {p.famille || "—"}
              </div>
              <h1 className="h-display" style={{ fontSize: isMobile ? "clamp(44px, 12vw, 72px)" : "clamp(72px, 9vw, 120px)", lineHeight: 0.92, letterSpacing: "-0.02em", margin: 0 }}>
                {p.nom.split(" ").map((w, i) => (
                  <span key={i} style={{ display: "block" }}>{i === 1 ? <em style={{ fontStyle: "italic", color: "var(--moss-deep)" }}>{w}</em> : w}</span>
                ))}
              </h1>
              <div style={{ display: "flex", alignItems: "baseline", gap: 12, marginTop: isMobile ? 14 : 24, flexWrap: "wrap" }}>
                <span className="h-italic" style={{ fontSize: isMobile ? 24 : 38, color: "var(--moss-deep)" }}>{p.latin}</span>
                <span style={{ fontFamily: "var(--mono)", fontSize: 12, letterSpacing: ".14em", color: "var(--ink-mute)" }}>{p.auteur}</span>
              </div>
            </div>

            <div style={{ ...fStyles.metaCard, width: isMobile ? "100%" : 280 }}>
              <MetaRow k="Famille" v={p.famille || "—"} />
              <MetaRow k="Genre" v={p.genre || "—"} />
              <MetaRow k="Statut" v={p.statut || "—"} />
              <MetaRow k="Origine" v={Array.isArray(p.origine) && p.origine.length ? p.origine.join(" · ") : "—"} />
              <MetaRow k="Hauteur" v={p.hauteur || "—"} />
              <MetaRow k="Altitude" v={p.altitude || "—"} />
              <MetaRow k="Floraison" v={hasFloraison ? `${p.floraison[0]} – ${p.floraison[p.floraison.length-1]}` : "—"} />
              <MetaRow k="Couleur" v={p.couleurFleur || p.couleur || "—"} last />
            </div>
          </div>

          {/* Photos */}
          {isMobile ? (
            <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              <FichePhotoSlot src={photoMain} label={`${p.latin.toUpperCase()} · PLANCHE PRINCIPALE`} aspect="4/3" isMain credit={mainCredit} photoKey={mainKey} canChoose={canChooseDefault} onSetDefault={handleSetDefault} onClick={() => openLightboxFor(mainKey)} />
              {(photoDetail1 || photoDetail2) && (
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
                  {photoDetail1 && <FichePhotoSlot src={photoDetail1} label="DÉTAIL" aspect="3/4" photoKey={otherKeys[0]} canChoose={canChooseDefault} onSetDefault={handleSetDefault} onClick={() => openLightboxFor(otherKeys[0])} />}
                  {photoDetail2 && <FichePhotoSlot src={photoDetail2} label="DÉTAIL" aspect="3/4" photoKey={otherKeys[1]} canChoose={canChooseDefault} onSetDefault={handleSetDefault} onClick={() => openLightboxFor(otherKeys[1])} />}
                </div>
              )}
            </div>
          ) : (
            <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 24, marginTop: 20 }}>
              <FichePhotoSlot src={photoMain} label={`${p.latin.toUpperCase()} · PLANCHE PRINCIPALE`} aspect="16/10" isMain credit={mainCredit} photoKey={mainKey} canChoose={canChooseDefault} onSetDefault={handleSetDefault} onClick={() => openLightboxFor(mainKey)} />
              <div style={{ display: "grid", gridTemplateRows: "1fr 1fr", gap: 24 }}>
                <FichePhotoSlot src={photoDetail1} label="DÉTAIL — CAPITULE" aspect="auto" photoKey={otherKeys[0]} canChoose={canChooseDefault} onSetDefault={handleSetDefault} onClick={() => openLightboxFor(otherKeys[0])} />
                <FichePhotoSlot src={photoDetail2} label="DÉTAIL — FEUILLE" aspect="auto" photoKey={otherKeys[1]} canChoose={canChooseDefault} onSetDefault={handleSetDefault} onClick={() => openLightboxFor(otherKeys[1])} />
              </div>
            </div>
          )}

          {/* Tags + actions */}
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: isMobile ? "flex-start" : "center", marginTop: isMobile ? 20 : 40, paddingTop: isMobile ? 18 : 28, borderTop: "1px solid var(--line)", flexDirection: isMobile ? "column" : "row", gap: isMobile ? 16 : 0 }}>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              {tags.length > 0
                ? tags.map(t => <span key={t} className="tag">{t}</span>)
                : <span style={{ fontFamily: "var(--serif)", fontStyle: "italic", color: "var(--ink-mute)", fontSize: 14 }}>Aucun usage renseigné.</span>}
            </div>
            {/* Bouton "Imprimer" temporairement caché (option non utilisée pour l'instant).
                Pour le réactiver : restaurer le <button> Imprimer dans les deux variantes
                (mobile : icône 44x44 entre Enrichir et Supprimer ; desktop : entre Mode
                lecture et Enrichir). Le state `printOpen` et le composant <FichePrint>
                sont conservés intacts plus bas. */}
            {isMobile ? (
              <div style={{ display: "flex", gap: 10, width: "100%", alignItems: "stretch" }}>
                <button
                  className="btn btn-solid"
                  onClick={goEdit}
                  disabled={!canEdit}
                  title={canEdit ? "" : "Connectez-vous pour enrichir cette fiche"}
                  style={{ flex: 1, justifyContent: "center", opacity: canEdit ? 1 : 0.4 }}
                >
                  Enrichir
                </button>
                {canEdit && (
                  <button
                    className="btn"
                    onClick={() => setQuickPhotoOpen(true)}
                    aria-label="Ajouter une photo"
                    title="Ajouter une photo"
                    style={{ width: 44, padding: 0, display: "grid", placeItems: "center", flexShrink: 0 }}
                  >
                    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                      <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
                      <circle cx="12" cy="13" r="4"/>
                    </svg>
                  </button>
                )}
                {canDeleteFiche && (
                  <button
                    className="btn"
                    onClick={() => setDeleteOpen(true)}
                    aria-label="Supprimer"
                    title="Supprimer la fiche (réservé aux modérateurs)"
                    style={{ width: 44, padding: 0, display: "grid", placeItems: "center", flexShrink: 0, color: "var(--ink-mute)", borderColor: "var(--line)" }}
                  >
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                      <polyline points="3 6 5 6 21 6"/>
                      <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
                      <path d="M10 11v6M14 11v6"/>
                      <path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
                    </svg>
                  </button>
                )}
              </div>
            ) : (
              <div style={{ display: "flex", gap: 10 }}>
                <button className="btn" onClick={() => setReader(true)}><IconBook size={13} /> Mode lecture</button>
                {canEdit && (
                  <button className="btn" onClick={() => setQuickPhotoOpen(true)}>
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" style={{ marginRight: 6, verticalAlign: "-2px" }}>
                      <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
                      <circle cx="12" cy="13" r="4"/>
                    </svg>
                    Ajouter une photo
                  </button>
                )}
                <button
                  className="btn btn-solid"
                  onClick={goEdit}
                  disabled={!canEdit}
                  title={canEdit ? "" : "Connectez-vous pour enrichir cette fiche"}
                  style={{ opacity: canEdit ? 1 : 0.4 }}
                >Enrichir cette fiche</button>
                {canDeleteFiche && (
                  <button
                    className="btn"
                    style={{ color: "var(--ink-mute)", borderColor: "var(--line)" }}
                    onClick={() => setDeleteOpen(true)}
                    title="Supprimer la fiche (réservé aux modérateurs)"
                  >Supprimer</button>
                )}
              </div>
            )}
          </div>
        </div>
      </section>

      {/* INCIPIT */}
      {hasIncipit && (
        <section style={{ textAlign: "center", padding: isMobile ? "48px 20px" : "100px 40px", borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)", background: "var(--tint)" }}>
          <Reveal>
            <div style={{ maxWidth: 1100, margin: "0 auto", display: "flex", flexDirection: "column", alignItems: "center" }}>
              <Ornament size={isMobile ? 60 : 80} color="var(--moss-deep)" />
              <p className="h-italic" style={{ fontSize: isMobile ? 28 : 44, lineHeight: 1.25, color: "var(--ink)", margin: isMobile ? "22px 0" : "32px 0", maxWidth: 980 }}>
                « {p.incipit} »
              </p>
              <Ornament size={isMobile ? 60 : 80} color="var(--moss-deep)" />
            </div>
          </Reveal>
        </section>
      )}

      {/* Sommaire mobile — défilement horizontal */}
      {isMobile && (
        <div className="fiche-toc-mobile" style={{ overflowX: "auto", display: "flex", gap: 6, padding: "14px 20px", borderBottom: "1px solid var(--line-soft)", position: "sticky", top: 58, zIndex: 10, background: "var(--nav-bg)", backdropFilter: "blur(8px)" }}>
          {sections.map(s => (
            <a key={s.id} href={`#fiche-${s.id}`} style={{
              flexShrink: 0,
              padding: "6px 14px",
              fontFamily: "var(--mono)",
              fontSize: 10,
              letterSpacing: ".14em",
              textTransform: "uppercase",
              background: activeSection === s.id ? "var(--moss-deep)" : "transparent",
              color: activeSection === s.id ? "var(--paper)" : "var(--ink-mute)",
              border: "1px solid",
              borderColor: activeSection === s.id ? "var(--moss-deep)" : "var(--line)",
              whiteSpace: "nowrap",
              textDecoration: "none",
              transition: "all 220ms var(--ease)",
            }}>{s.label}</a>
          ))}
        </div>
      )}

      {/* SOMMAIRE STICKY + CORPS */}
      <section style={{ maxWidth: 1400, margin: "0 auto", padding: isMobile ? "0 20px" : "0 40px" }}>
        <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "240px 1fr", gap: isMobile ? 0 : 80, alignItems: "start" }}>
          {/* TOC bureau — masqué sur mobile */}
          {!isMobile && (
            <aside style={fStyles.toc}>
              <div className="kicker" style={{ marginBottom: 18 }}>Sommaire</div>
              <nav style={{ display: "flex", flexDirection: "column", gap: 12 }}>
                {sections.map(s => (
                  <a key={s.id} href={`#fiche-${s.id}`} style={{
                    fontFamily: "var(--serif)", fontSize: 17,
                    color: activeSection === s.id ? "var(--ink)" : "var(--ink-mute)",
                    paddingLeft: activeSection === s.id ? 16 : 0,
                    borderLeft: activeSection === s.id ? "1px solid var(--ink)" : "1px solid transparent",
                    transition: "all 320ms var(--ease)",
                  }}>{s.label}</a>
                ))}
              </nav>
              <div style={{ height: 1, background: "var(--line)", margin: "24px 0" }} />
              <div className="kicker" style={{ marginBottom: 14 }}>Folio</div>
              <div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--ink-soft)", lineHeight: 1.7 }}>
                {Object.keys(photos).length} photographie{Object.keys(photos).length > 1 ? "s" : ""}<br/>
                {(p.contributeurs || []).length} contributeur{(p.contributeurs || []).length > 1 ? "s" : ""}<br/>
                {p.updatedAt ? `Mise à jour : ${new Date(p.updatedAt).toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" })}` : "Non publiée"}
              </div>
            </aside>
          )}

          {/* Corps */}
          <div style={{ maxWidth: 820 }}>
            {/* Description — masquée pour les fiches arbres minimales sans morphologie */}
            {showDescription && (
            <article id="fiche-description" style={fSection}>
              <Reveal>
                <SectionTitle num={sectionNum("description")} title="Description morphologique" isMobile={isMobile} />
                {hasMorphologie
                  ? <p style={fProse}>{p.morphologie}</p>
                  : <EmptyHint goEdit={goEdit}>La morphologie de cette plante n'a pas encore été décrite.</EmptyHint>}
              </Reveal>
            </article>
            )}

            {/* La feuille — silhouettes des caractères morphologiques (data.feuille) */}
            {hasFeuille && window.LEAF_SILHOUETTES && (
            <article id="fiche-feuille" style={fSection}>
              <Reveal>
                <SectionTitle num={sectionNum("feuille")} title="La feuille" isMobile={isMobile} />
                <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr 1fr" : "repeat(4, 1fr)", gap: isMobile ? 18 : 24, marginTop: 8 }}>
                  {FEUILLE_CATS.map((cat) => {
                    const defs = window.LEAF_SILHOUETTES[cat] || [];
                    const keys = Array.isArray(p.feuille[cat]) ? p.feuille[cat] : [];
                    const items = keys.map(k => defs.find(d => d.key === k)).filter(Boolean);
                    if (!items.length) return null;
                    return (
                      <div key={cat}>
                        <div className="kicker" style={{ marginBottom: 12, color: "var(--ink-mute)" }}>{(window.LEAF_CAT_LABELS || {})[cat] || cat}</div>
                        <div style={{ display: "flex", flexWrap: "wrap", gap: 16 }}>
                          {items.map((it) => (
                            <div key={it.key} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 7, width: 60 }}>
                              <svg viewBox="0 0 24 32" width={42} height={56} style={{ color: "var(--moss-deep)" }} aria-hidden="true">
                                {it.d.map((dd, i) => (
                                  <path key={i} d={dd} fill={it.noFill ? "none" : "rgba(122,151,80,0.18)"} stroke="currentColor" strokeWidth={1.1} strokeLinejoin="round" strokeLinecap="round" />
                                ))}
                              </svg>
                              <span style={{ fontFamily: "var(--serif)", fontSize: 13.5, color: "var(--ink-soft)", textAlign: "center", lineHeight: 1.2 }}>{it.label}</span>
                            </div>
                          ))}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </Reveal>
            </article>
            )}

            {/* Habitat — masquée pour les fiches arbres minimales sans habitat/floraison */}
            {showHabitat && (
            <article id="fiche-habitat" style={fSection}>
              <Reveal>
                <SectionTitle num={sectionNum("habitat")} title="Habitat & répartition" isMobile={isMobile} />
                {p.habitat
                  ? <p style={fProse}>{p.habitat}</p>
                  : !p.repartition && <EmptyHint goEdit={goEdit}>Aucune information d'habitat ou de répartition pour le moment.</EmptyHint>}
                {p.repartition && (
                  <p style={fProse}><strong style={{ fontWeight: 500, color: "var(--ink)" }}>Aire de répartition :</strong> {p.repartition}</p>
                )}

                {hasFloraison && (
                  <div style={{ ...fStyles.calCard, marginTop: 28 }}>
                    <div className="kicker" style={{ marginBottom: 14 }}>Calendrier de floraison & récolte</div>
                    <div style={{ ...fStyles.calendar, overflowX: isMobile ? "auto" : "visible" }}>
                      {["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"].map((m, i) => {
                        const months = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"];
                        const isFlower = p.floraison.includes(months[i]);
                        return (
                          <div key={i} style={{
                            flex: isMobile ? "0 0 calc(100%/12)" : 1,
                            minWidth: isMobile ? 30 : "auto",
                            textAlign: "center",
                            padding: isMobile ? "12px 0" : "16px 0",
                            background: isFlower ? "var(--moss-deep)" : "var(--paper-2)",
                            color: isFlower ? "var(--paper)" : "var(--ink-mute)",
                            fontFamily: "var(--mono)", fontSize: isMobile ? 11 : 13, fontWeight: 600, letterSpacing: ".1em",
                            borderRight: i < 11 ? "1px solid rgba(255,255,255,0.1)" : "none"
                          }}>{m}</div>
                        );
                      })}
                    </div>
                    <div style={{ display: "flex", gap: 18, marginTop: 12, fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".14em", color: "var(--ink-mute)" }}>
                      <span style={{ display: "flex", alignItems: "center", gap: 8 }}><span style={{ width: 12, height: 12, background: "var(--moss-deep)" }} />FLORAISON</span>
                      <span style={{ display: "flex", alignItems: "center", gap: 8 }}><span style={{ width: 12, height: 12, background: "var(--paper-2)", border: "1px solid var(--line)" }} />HORS SAISON</span>
                    </div>
                  </div>
                )}
              </Reveal>
            </article>
            )}

            {/* Usages */}
            <article id="fiche-usages" style={fSection}>
              <Reveal>
                <SectionTitle num={sectionNum("usages")} title="Usages" isMobile={isMobile} />
                {!hasUsages && !hasPrecautions && (
                  <EmptyHint goEdit={goEdit}>Aucun usage ou précaution n'a encore été documenté.</EmptyHint>
                )}
                {hasComestible && (
                  <UsageBlock
                    title="Comestibilité"
                    niveau={p.comestibilite.niveau}
                    detail={p.comestibilite.detail}
                    color="var(--olive)"
                  />
                )}
                {hasMedicinal && (
                  <UsageBlock
                    title="Vertus médicinales"
                    niveau={p.medicinal.niveau}
                    detail={p.medicinal.detail}
                    color="var(--moss-deep)"
                    extra={Array.isArray(p.medicinal.composes) && p.medicinal.composes.length > 0 ? (
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 18 }}>
                        {p.medicinal.composes.map(c => <span key={c} className="tag">{c}</span>)}
                      </div>
                    ) : null}
                  />
                )}
                {hasBioindic && (
                  <UsageBlock
                    title="Bio-indication"
                    niveau="Lecture du sol"
                    detail={p.bioindication}
                    color="var(--terra)"
                  />
                )}
                {hasPrecautions && (
                  <div style={fStyles.warning}>
                    <div className="kicker" style={{ color: "var(--rust)", marginBottom: 10 }}>⚠ Précautions</div>
                    <p style={{ fontFamily: "var(--serif)", fontSize: 17, lineHeight: 1.55, color: "var(--ink-soft)", margin: 0 }}>{p.precautions}</p>
                  </div>
                )}
              </Reveal>
            </article>

            {/* Au jardin — section dédiée aux fiches arboricoles (data.arbre) */}
            {hasArbre && (
              <JardinSection
                id="fiche-arbre"
                arbre={p.arbre}
                num={sectionNum("arbre")}
                isMobile={isMobile}
              />
            )}

            {/* Histoire — masquée pour les fiches arbres minimales sans histoire/symbolique */}
            {showHistoire && (
            <article id="fiche-histoire" style={fSection}>
              <Reveal>
                <SectionTitle num={sectionNum("histoire")} title="Histoire & symbolique" isMobile={isMobile} />
                {!hasHistoire && !hasSymbolique && (
                  <EmptyHint goEdit={goEdit}>Aucun récit historique ni symbolique pour le moment.</EmptyHint>
                )}
                {hasHistoire && p.histoire.split("\n\n").map((para, idx) => (
                  <p key={idx} style={{ ...fProse, fontSize: isMobile ? 17 : 19, whiteSpace: "pre-line" }}>
                    {idx === 0 && !isMobile && (
                      <span className="h-display" style={{ fontSize: 72, lineHeight: 0.9, float: "left", marginRight: 14, marginTop: 8, color: "var(--moss-deep)" }}>{para[0]}</span>
                    )}
                    {renderEmphasis(idx === 0 && !isMobile ? para.slice(1) : para)}
                  </p>
                ))}
                {hasSymbolique && (
                  <p style={fProse}><strong style={{ fontWeight: 500, color: "var(--ink)" }}>Symbolique :</strong> {p.symbolique}</p>
                )}
              </Reveal>
            </article>
            )}

            {/* Recettes */}
            {hasRecettes && (
              <article id="fiche-recettes" style={fSection}>
                <Reveal>
                  <SectionTitle num={sectionNum("recettes")} title="Recettes & préparations" isMobile={isMobile} />
                  <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "repeat(2, 1fr)", gap: isMobile ? 16 : 24, marginTop: 8 }}>
                    {p.recettes.map((r, i) => (
                      <RecetteCard key={i} recette={r} isMobile={isMobile} />
                    ))}
                  </div>
                </Reveal>
              </article>
            )}

            {/* Notes */}
            {hasNotes && (
              <article id="fiche-notes" style={fSection}>
                <Reveal>
                  <SectionTitle num={sectionNum("notes")} title="Notes" isMobile={isMobile} />
                  <p style={{ fontFamily: "var(--serif)", fontSize: isMobile ? 17 : 19, lineHeight: 1.6, color: "var(--ink-soft)", margin: 0, whiteSpace: "pre-line", maxWidth: 760 }}>
                    {p.notes}
                  </p>
                </Reveal>
              </article>
            )}

            {/* Jardins */}
            <article id="fiche-jardins" style={fSection}>
              <Reveal>
                <SectionTitle num={sectionNum("jardins")} title="Jardins partagés où l'espèce est cultivée" isMobile={isMobile} />
                {hasJardins ? (
                  <div style={{ display: "grid", gap: 12 }}>
                    {p.jardins.map((j, i) => (
                      <div key={j.nom + ":" + i} style={{ ...fStyles.gardenRow, gridTemplateColumns: isMobile ? "32px 1fr auto" : "40px 1fr auto auto", gap: isMobile ? 14 : 24 }}>
                        <span style={{ fontFamily: "var(--mono)", fontSize: 10, color: "var(--ink-mute)", letterSpacing: ".14em" }}>{String(i+1).padStart(2,"0")}</span>
                        <div>
                          <div className="h-display" style={{ fontSize: isMobile ? 18 : 22 }}>{j.nom}</div>
                          {j.lieu && (
                            <div style={{ fontFamily: "var(--mono)", fontSize: 9, letterSpacing: ".14em", color: "var(--ink-mute)", textTransform: "uppercase", marginTop: 2 }}>{j.lieu}</div>
                          )}
                        </div>
                        <span className="tag"><span className="tag-dot" /> {j.parcelles || 1} parcelle{(j.parcelles || 1) > 1 ? "s" : ""}</span>
                        {!isMobile && <span style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".14em", color: "var(--moss-deep)" }}>VOIR LE JARDIN →</span>}
                      </div>
                    ))}
                  </div>
                ) : (
                  <EmptyHint goEdit={goEdit}>Cette plante n'est pour l'instant cultivée dans aucun jardin partagé recensé.</EmptyHint>
                )}
              </Reveal>
            </article>

            {/* Galerie */}
            {hasGalerie && (
              <article id="fiche-galerie" style={fSection}>
                <Reveal>
                  <SectionTitle num={sectionNum("galerie")} title="Galerie photographique" isMobile={isMobile} />
                  <p style={fProse}>{p.galerie.length} prise{p.galerie.length > 1 ? "s" : ""} de vue versée{p.galerie.length > 1 ? "s" : ""} par les contributeurs.</p>
                  <div style={{ display: "grid", gridTemplateColumns: isMobile ? "repeat(2, 1fr)" : "repeat(3, 1fr)", gap: isMobile ? 8 : 12, marginTop: 20 }}>
                    {p.galerie.map((g) => (
                      <button key={g.id} onClick={() => openLightboxFor(g.id)} style={{ padding: 0, border: 0, textAlign: "left", cursor: "zoom-in", background: "transparent" }}>
                        <Plate src={photos[g.id] || null} frame={false} label={(g.label || "").toUpperCase()} aspect="3/4" />
                      </button>
                    ))}
                  </div>
                </Reveal>
              </article>
            )}

            {/* Grappes — regroupements thématiques contenant cette plante */}
            {hasGrappes && (
              <article id="fiche-grappes" style={fSection}>
                <Reveal>
                  <SectionTitle num={sectionNum("grappes")} title="Grappes" isMobile={isMobile} />
                  <p style={fProse}>
                    {plantGrappes.length > 1
                      ? "Cette plante mûrit dans plusieurs grappes thématiques — des regroupements par affinité d'usage. Cliquez pour explorer chacune."
                      : "Cette plante appartient à une grappe thématique — un regroupement par affinité d'usage. Cliquez pour l'explorer."}
                  </p>
                  <div style={{ display: "flex", flexWrap: "wrap", gap: isMobile ? 10 : 12, marginTop: 14 }}>
                    {plantGrappes.map(g => (
                      <button
                        key={g.slug}
                        onClick={() => { if (setPoleSlug) setPoleSlug(g.slug); setRoute("poles"); }}
                        style={{
                          padding: "12px 18px",
                          background: "var(--cream)",
                          border: "1px solid var(--line)",
                          textAlign: "left",
                          cursor: "pointer",
                          fontFamily: "var(--serif)",
                          fontSize: isMobile ? 16 : 18,
                          color: "var(--moss-deep)",
                          transition: "all 240ms var(--ease)",
                        }}
                        onMouseEnter={e => { e.currentTarget.style.borderColor = "var(--moss-deep)"; e.currentTarget.style.transform = "translateY(-2px)"; }}
                        onMouseLeave={e => { e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.transform = "translateY(0)"; }}
                      >
                        {g.titre} <span aria-hidden="true">→</span>
                      </button>
                    ))}
                  </div>
                </Reveal>
              </article>
            )}

            {/* Voir aussi — cross-références vers d'autres fiches du recueil */}
            {hasVoirAussi && (
              <article id="fiche-voir-aussi" style={fSection}>
                <Reveal>
                  <SectionTitle num={sectionNum("voir-aussi")} title="Voir aussi" isMobile={isMobile} />
                  {p.voirAussiTitre && p.voirAussiTitre.trim() ? (
                    <p className="h-italic" style={{ fontSize: isMobile ? 20 : 24, lineHeight: 1.35, color: "var(--moss-deep)", margin: "0 0 18px" }}>
                      {p.voirAussiTitre.trim()}
                    </p>
                  ) : (
                    <p style={fProse}>Plantes liées à cette fiche dans le recueil — affinités thérapeutiques, familles d'usage, alternatives.</p>
                  )}
                  <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "repeat(3, 1fr)", gap: isMobile ? 12 : 16, marginTop: 14 }}>
                    {voirAussiItems.map(item => (
                      <button
                        key={item.id}
                        onClick={() => { if (setPlantId) setPlantId(item.id); setRoute("fiche"); }}
                        style={{
                          padding: "18px 20px",
                          background: "var(--cream)",
                          border: "1px solid var(--line)",
                          textAlign: "left",
                          cursor: "pointer",
                          fontFamily: "inherit",
                          transition: "all 240ms var(--ease)",
                          display: "flex",
                          flexDirection: "column",
                          gap: 4,
                        }}
                        onMouseEnter={e => { e.currentTarget.style.borderColor = "var(--moss-deep)"; e.currentTarget.style.transform = "translateY(-2px)"; }}
                        onMouseLeave={e => { e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.transform = "translateY(0)"; }}
                      >
                        <div className="kicker" style={{ marginBottom: 4, color: "var(--moss-deep)" }}>{item.famille || "—"}</div>
                        <div className="h-display" style={{ fontSize: 22, lineHeight: 1.1 }}>{item.nom}</div>
                        <div className="h-italic" style={{ fontSize: 14, color: "var(--ink-soft)" }}>{item.latin}</div>
                        {Array.isArray(item.usage) && item.usage.length > 0 && (
                          <div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginTop: 8 }}>
                            {item.usage.slice(0, 3).map(u => (
                              <span key={u} style={{ fontFamily: "var(--mono)", fontSize: 9, letterSpacing: ".10em", color: "var(--ink-mute)", textTransform: "uppercase", padding: "2px 6px", border: "1px solid var(--line-soft)" }}>{u}</span>
                            ))}
                          </div>
                        )}
                      </button>
                    ))}
                  </div>
                </Reveal>
              </article>
            )}

            {/* Contributeurs */}
            <article id="fiche-contribs" style={{ ...fSection, paddingBottom: 60 }}>
              <Reveal>
                <SectionTitle num={sectionNum("contribs")} title="Contributeurs" isMobile={isMobile} />
                {hasContribs ? (
                  <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "repeat(3, 1fr)", gap: isMobile ? 10 : 18 }}>
                    {p.contributeurs.map((c, i) => (
                      <div key={(c.nom || "?") + ":" + i} style={{ ...fStyles.contribCard, display: "flex", flexDirection: isMobile ? "row" : "column", alignItems: isMobile ? "center" : "flex-start", gap: isMobile ? 14 : 0 }}>
                        <div style={{ width: isMobile ? 38 : 48, height: isMobile ? 38 : 48, borderRadius: "50%", background: "var(--paper-3)", display: "grid", placeItems: "center", fontFamily: "var(--display)", fontSize: isMobile ? 18 : 22, color: "var(--moss-deep)", marginBottom: isMobile ? 0 : 14, flexShrink: 0 }}>
                          {(c.nom || "?")[0]}
                        </div>
                        <div>
                          <div className="h-display" style={{ fontSize: isMobile ? 18 : 20 }}>{c.nom}</div>
                          <div style={{ fontFamily: "var(--serif)", fontSize: 14, fontStyle: "italic", color: "var(--ink-soft)", marginTop: 2 }}>{c.role}</div>
                          <div style={{ fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".14em", color: "var(--ink-mute)", marginTop: 6 }}>{c.date}</div>
                        </div>
                      </div>
                    ))}
                  </div>
                ) : (
                  <EmptyHint goEdit={goEdit}>Cette fiche n'a pas encore de contributeur·rice.</EmptyHint>
                )}
              </Reveal>
            </article>
          </div>
        </div>
      </section>

      {printOpen && ReactDOM.createPortal(<FichePrint plant={p} onClose={() => setPrintOpen(false)} />, document.body)}
      {reader && ReactDOM.createPortal(<ReaderMode plant={p} onClose={() => setReader(false)} />, document.body)}
      {quickPhotoOpen && ReactDOM.createPortal(
        <QuickPhotoModal
          plant={p}
          onClose={() => setQuickPhotoOpen(false)}
          onSaved={(next) => {
            setPlant(next);
            const idx = window.PLANTS.findIndex(pp => pp.id === next.id);
            if (idx >= 0) window.PLANTS[idx] = { ...window.PLANTS[idx], ...next };
            setQuickPhotoOpen(false);
          }}
        />,
        document.body
      )}
      {deleteOpen && ReactDOM.createPortal(
        <div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "rgba(0,0,0,0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9999 }}>
          <div style={{ background: "var(--paper)", padding: 40, maxWidth: 440, width: "90%", border: "1px solid var(--line)" }}>
            <h3 className="h-display" style={{ fontSize: 26, marginBottom: 12 }}>Supprimer cette fiche ?</h3>
            <p style={{ fontFamily: "var(--serif)", fontSize: 16, color: "var(--ink-soft)", marginBottom: 24, lineHeight: 1.5 }}>
              Cette action est irréversible. Entrez le mot de passe pour confirmer la suppression de <em>{p.nom}</em>.
            </p>
            <input
              type="password"
              placeholder="Mot de passe"
              value={deleteSecret}
              onChange={e => { setDeleteSecret(e.target.value); setDeleteError(null); }}
              onKeyDown={e => e.key === "Enter" && !deleting && deleteSecret && handleDelete()}
              style={{ width: "100%", padding: "10px 14px", fontFamily: "var(--serif)", fontSize: 16, border: "1px solid var(--line)", background: "var(--cream)", marginBottom: 8, boxSizing: "border-box" }}
            />
            {deleteError && (
              <p style={{ color: "#c0392b", fontFamily: "var(--mono)", fontSize: 12, marginBottom: 12 }}>{deleteError}</p>
            )}
            <div style={{ display: "flex", gap: 10, justifyContent: "flex-end", marginTop: 16 }}>
              <button className="btn" onClick={() => { setDeleteOpen(false); setDeleteSecret(""); setDeleteError(null); }}>Annuler</button>
              <button
                className="btn btn-solid"
                style={{ background: "#c0392b", borderColor: "#c0392b" }}
                onClick={handleDelete}
                disabled={deleting || !deleteSecret}
              >
                {deleting ? "Suppression…" : "Confirmer la suppression"}
              </button>
            </div>
          </div>
        </div>,
        document.body
      )}
      {lightbox !== null && lightboxItems.length > 0 && ReactDOM.createPortal(
        <Lightbox
          items={lightboxItems}
          index={Math.min(lightbox, lightboxItems.length - 1)}
          onClose={() => setLightbox(null)}
          setIndex={setLightbox}
          onDelete={canEdit ? handleDeletePhoto : undefined}
        />,
        document.body
      )}
    </main>
  );
}

function EmptyHint({ children, goEdit }) {
  return (
    <div style={{
      padding: "18px 22px",
      background: "rgba(45,67,41,0.04)",
      border: "1px dashed var(--line)",
      fontFamily: "var(--serif)",
      fontStyle: "italic",
      color: "var(--ink-mute)",
      fontSize: 16,
      lineHeight: 1.5,
    }}>
      {children}{" "}
      <button onClick={goEdit} style={{
        background: "none", border: 0, padding: 0, cursor: "pointer",
        fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".16em", textTransform: "uppercase",
        color: "var(--moss-deep)", textDecoration: "underline", textUnderlineOffset: 4,
      }}>Enrichir cette fiche →</button>
    </div>
  );
}

function MetaRow({ k, v, last }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", padding: "10px 0", borderBottom: last ? "none" : "1px solid var(--line-soft)", gap: 12 }}>
      <span style={{ fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".16em", color: "var(--ink-mute)", textTransform: "uppercase" }}>{k}</span>
      <span style={{ fontFamily: "var(--serif)", fontSize: 14, color: "var(--ink)", textAlign: "right" }}>{v}</span>
    </div>
  );
}

const TYPE_COLORS = {
  "Remède":   { bg: "#eef5ee", border: "#a8c5a0", text: "#3a5e35" },
  "Cuisine":  { bg: "#fdf6ec", border: "#d4a96a", text: "#7a4f1e" },
  "Boisson":  { bg: "#eef2f8", border: "#93aad4", text: "#2c3e6b" },
  "Beauté":   { bg: "#f8eef5", border: "#c49abf", text: "#6b2c60" },
};

function RecetteCard({ recette: r, isMobile }) {
  const colors = TYPE_COLORS[r.type] || TYPE_COLORS["Cuisine"];
  return (
    <div style={{ background: "var(--paper-2)", border: "1px solid var(--line-soft)", borderRadius: 2, padding: isMobile ? "20px 18px" : "28px 28px 24px" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, marginBottom: 16 }}>
        <h3 className="h-display" style={{ fontSize: isMobile ? 20 : 24, lineHeight: 1.1, margin: 0 }}>{r.titre}</h3>
        {r.type && (
          <span style={{ fontFamily: "var(--mono)", fontSize: 9, letterSpacing: ".16em", textTransform: "uppercase", padding: "3px 8px", background: colors.bg, border: `1px solid ${colors.border}`, color: colors.text, whiteSpace: "nowrap", flexShrink: 0 }}>{r.type}</span>
        )}
      </div>
      <div style={{ marginBottom: 16 }}>
        <div style={{ fontFamily: "var(--mono)", fontSize: 9, letterSpacing: ".18em", textTransform: "uppercase", color: "var(--ink-mute)", marginBottom: 8 }}>Ingrédients</div>
        <ul style={{ margin: 0, paddingLeft: 0, listStyle: "none" }}>
          {r.ingredients.map((ing, i) => (
            <li key={i} style={{ fontFamily: "var(--serif)", fontSize: 14, color: "var(--ink-soft)", lineHeight: 1.5, paddingLeft: 14, position: "relative", marginBottom: 4 }}>
              <span style={{ position: "absolute", left: 0, top: 7, width: 4, height: 4, borderRadius: "50%", background: "var(--moss-mid)", display: "block" }} />
              {ing}
            </li>
          ))}
        </ul>
      </div>
      <div style={{ marginBottom: r.note ? 16 : 0 }}>
        <div style={{ fontFamily: "var(--mono)", fontSize: 9, letterSpacing: ".18em", textTransform: "uppercase", color: "var(--ink-mute)", marginBottom: 8 }}>Préparation</div>
        <ol style={{ margin: 0, paddingLeft: 0, listStyle: "none", counterReset: "step" }}>
          {r.etapes.map((etape, i) => (
            <li key={i} style={{ fontFamily: "var(--serif)", fontSize: 14, color: "var(--ink-soft)", lineHeight: 1.55, display: "flex", gap: 10, marginBottom: 6 }}>
              <span style={{ fontFamily: "var(--mono)", fontSize: 10, color: "var(--moss-deep)", flexShrink: 0, marginTop: 2 }}>{String(i + 1).padStart(2, "0")}.</span>
              {etape}
            </li>
          ))}
        </ol>
      </div>
      {r.note && (
        <p style={{ fontFamily: "var(--serif)", fontSize: 13, fontStyle: "italic", color: "var(--ink-mute)", margin: 0, paddingTop: 12, borderTop: "1px solid var(--line-soft)" }}>{r.note}</p>
      )}
    </div>
  );
}

function FichePhotoSlot({ src, label, aspect, isMain = false, credit = null, photoKey, canChoose, onSetDefault, onClick }) {
  const [hover, setHover] = React.useState(false);
  const fill = aspect === "auto";
  const clickable = !!src && typeof onClick === "function";
  return (
    <div
      style={{ position: "relative", ...(fill ? { height: "100%" } : {}), cursor: clickable ? "zoom-in" : "default" }}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      onClick={clickable ? onClick : undefined}
      role={clickable ? "button" : undefined}
      tabIndex={clickable ? 0 : undefined}
      onKeyDown={clickable ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(); } } : undefined}
      aria-label={clickable ? `Zoomer sur ${label}` : undefined}
    >
      <Plate src={src} frame={false} label={label} aspect={aspect} style={{ ...(fill ? { height: "100%" } : {}) }} />
      {isMain && src && (
        <div style={{ position: "absolute", top: 8, left: 8, background: "rgba(255,255,255,0.88)", padding: "3px 8px", fontFamily: "var(--mono)", fontSize: 8, letterSpacing: ".14em", color: "var(--moss-deep)", pointerEvents: "none" }}>
          ★ PRINCIPALE
        </div>
      )}
      {isMain && src && credit && (credit.text || credit.url) && (
        <div style={{ position: "absolute", bottom: 8, left: 8, maxWidth: "70%", background: "rgba(255,255,255,0.86)", padding: "2px 7px", fontFamily: "var(--mono)", fontSize: 8, letterSpacing: ".08em", color: "var(--moss-deep)", lineHeight: 1.4 }}>
          {credit.url ? (
            <a href={credit.url} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()} style={{ color: "inherit", textDecoration: "none" }} title="Source / licence">
              © {credit.text || credit.url} ↗
            </a>
          ) : (
            <span>© {credit.text}</span>
          )}
        </div>
      )}
      {clickable && hover && (
        <div style={{ position: "absolute", top: 8, right: 8, background: "rgba(28,36,27,0.78)", color: "#f1ead9", width: 32, height: 32, borderRadius: "50%", display: "grid", placeItems: "center", pointerEvents: "none" }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="11" cy="11" r="7"/>
            <line x1="21" y1="21" x2="16.65" y2="16.65"/>
            <line x1="11" y1="8" x2="11" y2="14"/>
            <line x1="8" y1="11" x2="14" y2="11"/>
          </svg>
        </div>
      )}
      {canChoose && !isMain && src && hover && (
        <button
          onClick={(e) => { e.stopPropagation(); onSetDefault(photoKey); }}
          style={{ position: "absolute", bottom: 8, right: 8, background: "rgba(255,255,255,0.92)", border: "1px solid var(--line)", padding: "5px 10px", fontFamily: "var(--mono)", fontSize: 8, letterSpacing: ".13em", color: "var(--moss-deep)", cursor: "pointer", textTransform: "uppercase" }}
        >
          ★ Définir principale
        </button>
      )}
    </div>
  );
}

function SectionTitle({ num, title, isMobile }) {
  return (
    <header style={{ marginBottom: isMobile ? 20 : 28, display: "flex", alignItems: "baseline", gap: 14 }}>
      <span className="h-italic" style={{ fontSize: isMobile ? 22 : 28, color: "var(--moss-deep)" }}>{num}</span>
      <h2 className="h-display" style={{ fontSize: isMobile ? 32 : 46, lineHeight: 1, margin: 0 }}>{title}</h2>
    </header>
  );
}

function UsageBlock({ title, niveau, detail, color, extra }) {
  return (
    <div style={{ padding: "24px 0", borderTop: "1px solid var(--line-soft)" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 12, flexWrap: "wrap", gap: 8 }}>
        <h3 className="h-display" style={{ fontSize: 26, margin: 0 }}>{title}</h3>
        <span style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".14em", color, textTransform: "uppercase" }}>{niveau}</span>
      </div>
      <div>
        {String(detail).split(/\\n|\n/).map((line, i) => line.trim() ? (
          <p key={i} style={{ fontFamily: "var(--serif)", fontSize: 17, lineHeight: 1.6, color: "var(--ink-soft)", margin: i === 0 ? 0 : "12px 0 0" }}>{line.trim()}</p>
        ) : null)}
      </div>
      {extra}
    </div>
  );
}

function ReaderMode({ plant, onClose }) {
  return (
    <div style={readerStyles.wrap} className="page-enter">
      <button onClick={onClose} style={readerStyles.close} aria-label="Fermer"><IconClose size={18} /></button>
      <div style={readerStyles.inner}>
        <div className="kicker" style={{ marginBottom: 24 }}>Mode lecture</div>
        <h1 className="h-display" style={{ fontSize: "clamp(52px, 10vw, 92px)", lineHeight: 0.95, margin: "0 0 16px", letterSpacing: "-0.02em" }}>
          {plant.nom}
        </h1>
        <div className="h-italic" style={{ fontSize: 24, color: "var(--moss-deep)", marginBottom: 40 }}>{plant.latin} {plant.auteur || ""}</div>
        {plant.incipit && (
          <p className="h-italic" style={{ fontSize: 22, lineHeight: 1.4, color: "var(--ink)", marginBottom: 36 }}>« {plant.incipit} »</p>
        )}
        {plant.morphologie && (
          <p style={{ fontFamily: "var(--serif)", fontSize: 19, lineHeight: 1.7, color: "var(--ink-soft)", marginBottom: 24 }}>{plant.morphologie}</p>
        )}
        {plant.habitat && (
          <p style={{ fontFamily: "var(--serif)", fontSize: 19, lineHeight: 1.7, color: "var(--ink-soft)", marginBottom: 24 }}>{plant.habitat}</p>
        )}
        {plant.histoire && (
          <p style={{ fontFamily: "var(--serif)", fontSize: 19, lineHeight: 1.7, color: "var(--ink-soft)", marginBottom: 24 }}>
            <strong style={{ fontWeight: 500, color: "var(--ink)" }}>Histoire — </strong>{plant.histoire}
          </p>
        )}
        {!plant.morphologie && !plant.habitat && !plant.histoire && (
          <p style={{ fontFamily: "var(--serif)", fontSize: 18, fontStyle: "italic", color: "var(--ink-mute)" }}>
            Cette fiche n'a pas encore de contenu rédigé.
          </p>
        )}
      </div>
    </div>
  );
}

function Lightbox({ items, index, setIndex, onClose, onDelete }) {
  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      if (e.key === "ArrowRight") setIndex((index + 1) % items.length);
      if (e.key === "ArrowLeft") setIndex((index - 1 + items.length) % items.length);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [index]);
  const current = items[index] || {};
  const hasMultiple = items.length > 1;
  // Bouton "Supprimer" : photos uploadées (p1-p6) OU photo Wikipedia auto.
  // Pour wiki, la suppression est durable côté serveur (api/save respecte
  // `wikipedia: null` explicite et ne refetche pas tant qu'une photo locale
  // existe ou que la suppression est demandée).
  const canDelete = !!onDelete && current.id && (/^p[1-6]$/.test(current.id) || current.id === "wiki");
  return (
    <div style={lightStyles.wrap} onClick={onClose}>
      <button style={lightStyles.close} onClick={onClose} aria-label="Fermer"><IconClose size={20} /></button>
      {hasMultiple && (
        <>
          <button
            style={{ ...lightStyles.nav, left: 16 }}
            aria-label="Photo précédente"
            onClick={(e) => { e.stopPropagation(); setIndex((index - 1 + items.length) % items.length); }}
          ><IconArrow size={16} rot={180} /></button>
          <button
            style={{ ...lightStyles.nav, right: 16 }}
            aria-label="Photo suivante"
            onClick={(e) => { e.stopPropagation(); setIndex((index + 1) % items.length); }}
          ><IconArrow size={16} /></button>
        </>
      )}
      <div style={lightStyles.frame} onClick={(e) => e.stopPropagation()}>
        <div style={{
          width: "min(92vw, 1100px)",
          height: "min(82vh, 1300px)",
          background: "#2d4329",
          padding: 24,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          boxSizing: "border-box",
        }}>
          {current.src ? (
            <img
              src={current.src}
              alt={current.label || ""}
              style={{
                maxWidth: "100%",
                maxHeight: "100%",
                width: "auto",
                height: "auto",
                objectFit: "contain",
                display: "block",
              }}
            />
          ) : (
            <div style={{ color: "rgba(241,234,217,0.6)", fontFamily: "var(--mono)", fontSize: 12, letterSpacing: ".14em" }}>
              IMAGE INDISPONIBLE
            </div>
          )}
        </div>
        <div style={{ marginTop: 14, color: "var(--paper)", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 14, fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".16em" }}>
          <span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{current.label || ""}</span>
          {canDelete && (
            <button
              onClick={(e) => { e.stopPropagation(); onDelete(current.id); }}
              title={`Supprimer cette photo (${current.id.toUpperCase()})`}
              aria-label="Supprimer cette photo"
              style={{
                display: "inline-flex", alignItems: "center", gap: 6,
                padding: "5px 10px",
                background: "transparent",
                border: "1px solid rgba(241,234,217,0.4)",
                color: "rgba(241,234,217,0.85)",
                fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".14em",
                textTransform: "uppercase", cursor: "pointer",
                transition: "all 200ms var(--ease)",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = "#c0392b"; e.currentTarget.style.color = "#ff7a6d"; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = "rgba(241,234,217,0.4)"; e.currentTarget.style.color = "rgba(241,234,217,0.85)"; }}
            >
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
                <polyline points="3 6 5 6 21 6"/>
                <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
                <path d="M10 11v6M14 11v6"/>
                <path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
              </svg>
              Supprimer
            </button>
          )}
          <span style={{ flexShrink: 0 }}>{index + 1} / {items.length}</span>
        </div>
      </div>
    </div>
  );
}

const fStyles = {
  metaCard: { background: "var(--cream)", border: "1px solid var(--line)", padding: "8px 20px", flex: "none" },
  toc: { position: "sticky", top: 110, paddingTop: 80 },
  calCard: { padding: 20, background: "var(--cream)", border: "1px solid var(--line)" },
  calendar: { display: "flex", border: "1px solid var(--line)" },
  warning: { marginTop: 28, padding: "20px 22px", background: "rgba(168,116,77,0.08)", border: "1px solid rgba(139,74,43,0.25)", borderLeft: "3px solid var(--rust)" },
  gardenRow: { display: "grid", alignItems: "center", padding: "18px 20px", background: "var(--cream)", border: "1px solid var(--line-soft)", cursor: "pointer", transition: "all 280ms var(--ease)" },
  contribCard: { padding: 20, background: "var(--cream)", border: "1px solid var(--line-soft)" },
};
const fSection = { padding: "56px 0", borderTop: "1px solid var(--line-soft)" };
const fProse = { fontFamily: "var(--serif)", fontSize: 18, lineHeight: 1.65, color: "var(--ink-soft)", marginBottom: 16 };

const readerStyles = {
  wrap: { position: "fixed", inset: 0, background: "var(--paper)", zIndex: 1000, overflowY: "auto", padding: "80px 0" },
  inner: { maxWidth: 680, margin: "0 auto", padding: "0 24px" },
  close: { position: "fixed", top: 20, right: 20, width: 44, height: 44, border: "1px solid var(--ink)", borderRadius: "50%", display: "grid", placeItems: "center", background: "var(--paper)", zIndex: 1001 },
};

const lightStyles = {
  wrap: { position: "fixed", inset: 0, background: "rgba(15,20,15,0.94)", backdropFilter: "blur(8px)", zIndex: 2000, display: "grid", placeItems: "center" },
  close: { position: "absolute", top: 20, right: 20, width: 44, height: 44, border: "1px solid var(--paper)", borderRadius: "50%", display: "grid", placeItems: "center", background: "transparent", color: "var(--paper)", zIndex: 2001 },
  nav: { position: "absolute", top: "50%", transform: "translateY(-50%)", width: 48, height: 48, border: "1px solid var(--paper)", borderRadius: "50%", background: "transparent", color: "var(--paper)", display: "grid", placeItems: "center" },
  frame: { textAlign: "left" },
};

// ─────────────────────────────────────────────────────────────────────────────
// QuickPhotoModal — bouton "+ Ajouter une photo" depuis la fiche.
// UX rapide : drop ou click → preview → choix du slot (par défaut premier libre)
// → option "★ Définir comme photo principale" → save. Pas de wizard 7 étapes.
//
// Sécurité : récupère les photos existantes via api/photos AVANT de sauver pour
// fusionner et ne PAS écraser les anciennes photos (même bug que l'Editor qui
// a été corrigé). Le set-default-photo passe par l'endpoint dédié
// /api/set-default-photo plutôt que par /api/save (qui pourrait écraser).
// ─────────────────────────────────────────────────────────────────────────────

function QuickPhotoModal({ plant, onClose, onSaved }) {
  const slots = [
    { id: "p1", label: "Planche principale" },
    { id: "p2", label: "Fleur / capitule" },
    { id: "p3", label: "Feuille — détail" },
    { id: "p4", label: "Tige / port" },
    { id: "p5", label: "Habitat" },
    { id: "p6", label: "Vue complémentaire" },
  ];

  const [photos, setPhotos] = React.useState({});
  const [loading, setLoading] = React.useState(true);
  const [pendingURL, setPendingURL] = React.useState(null);
  const [pendingSlot, setPendingSlot] = React.useState(null);
  const [makeDefault, setMakeDefault] = React.useState(false);
  const [over, setOver] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [pendingNotice, setPendingNotice] = React.useState(null);
  const fileRef = React.useRef(null);

  // Chargement des photos existantes au montage
  React.useEffect(() => {
    fetch(`api/photos?id=${encodeURIComponent(plant.id)}`)
      .then(r => r.ok ? r.json() : {})
      .then(p => {
        const ph = (p && !p.error && typeof p === "object") ? p : {};
        setPhotos(ph);
        // Premier slot libre = défaut
        const firstFree = slots.find(s => !ph[s.id])?.id || "p6";
        setPendingSlot(firstFree);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [plant.id]);

  const handleFile = async (file) => {
    setError(null);
    if (!file) return;
    if (!/^image\/(png|jpeg|webp|avif)$/i.test(file.type)) {
      setError("Format accepté : PNG, JPEG, WebP, AVIF.");
      return;
    }
    if (file.size > 20 * 1024 * 1024) {
      setError("Image trop lourde (max 20 Mo en entrée — la compression la réduit ensuite).");
      return;
    }
    setBusy(true);
    try {
      // Utilise la compression partagée (shared.jsx) : redimensionnement à
      // 1500 px max + recompression WebP qualité 0.82. Une photo téléphone
      // 4 Mo passe à ~200 Ko. Le slot pré-sélectionné reste modifiable.
      const url = await window.compressImage(file, { maxDim: 1500, quality: 0.82 });
      setPendingURL(url);
    } catch {
      setError("Impossible de lire l'image (format non supporté ou fichier corrompu).");
    } finally {
      setBusy(false);
    }
  };

  const handleSave = async () => {
    if (!pendingURL || !pendingSlot) return;
    setBusy(true);
    setError(null);
    try {
      // 1. Re-fetch la fiche complète pour préserver tous les champs texte
      const fiche = await fetch(`api/get?id=${encodeURIComponent(plant.id)}`, { cache: "no-store" }).then(r => r.json());
      // 2. Fusionner : anciennes photos + nouvelle photo dans le slot choisi
      const nextPhotos = { ...photos, [pendingSlot]: pendingURL };
      delete nextPhotos.error; // au cas où
      fiche.photos = nextPhotos;
      if (makeDefault) fiche.photoDefault = pendingSlot;
      delete fiche.wikipedia; // évite que la wikipedia auto réécrase la photo principale
      // 3. Sauver
      const res = await (window.authedFetch || fetch)("api/save", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(fiche),
      });
      const json = await res.json();

      // Phase 2 : 409 = autre révision pending déjà active
      if (res.status === 409) {
        setError("Une autre proposition est déjà en cours de modération sur cette fiche. " +
          "Vous pourrez resoumettre après validation ou retrait.");
        setBusy(false);
        return;
      }
      if (!res.ok || !json.ok) throw new Error(json.error || "Erreur d'enregistrement.");

      // Phase 2 : pending = contributeur. La fiche live n'est pas modifiée,
      // on n'appelle pas onSaved (qui mettrait à jour le state local).
      if (json.status === "pending") {
        setPendingNotice("Proposition envoyée à la modération. Vous serez informé du résultat.");
        // Le modal reste ouvert quelques secondes pour afficher la confirmation
        setTimeout(() => onClose(), 2500);
        return;
      }

      // status === "applied" (moderator+) : comportement historique
      onSaved({
        ...plant,
        photos: nextPhotos,
        photoDefault: makeDefault ? pendingSlot : plant.photoDefault,
      });
    } catch (e) {
      setError(e.message || "Erreur réseau.");
      setBusy(false);
    }
  };

  const slotIsOccupied = pendingSlot && photos[pendingSlot] && pendingURL == null;

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9999, padding: 20 }} onClick={onClose}>
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          background: "var(--paper)",
          padding: "32px 36px",
          maxWidth: 580,
          width: "100%",
          maxHeight: "92vh",
          overflowY: "auto",
          border: "1px solid var(--line)",
        }}
      >
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 22 }}>
          <h3 className="h-display" style={{ fontSize: 28, margin: 0 }}>Ajouter une photo</h3>
          <button
            onClick={onClose}
            aria-label="Fermer"
            style={{ background: "transparent", border: 0, fontSize: 26, color: "var(--ink-mute)", cursor: "pointer", lineHeight: 1, padding: 4 }}
          >×</button>
        </div>

        <p style={{ fontFamily: "var(--serif)", fontSize: 15, color: "var(--ink-soft)", margin: "0 0 22px", lineHeight: 1.5 }}>
          Pour <em>{plant.nom}</em>. Glisse une image ou clique sur la zone, puis choisis le slot et confirme.
        </p>

        {/* Drop zone */}
        <input
          ref={fileRef}
          type="file"
          accept="image/png,image/jpeg,image/webp,image/avif"
          style={{ display: "none" }}
          onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) handleFile(f); e.target.value = ""; }}
        />
        <div
          onClick={() => fileRef.current && fileRef.current.click()}
          onDragEnter={(e) => { e.preventDefault(); e.stopPropagation(); setOver(true); }}
          onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }}
          onDragLeave={(e) => { e.preventDefault(); e.stopPropagation(); setOver(false); }}
          onDrop={(e) => {
            e.preventDefault(); e.stopPropagation(); setOver(false);
            const f = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];
            if (f) handleFile(f);
          }}
          style={{
            cursor: "pointer",
            border: "1px dashed " + (over ? "var(--moss-deep)" : "var(--line)"),
            background: pendingURL ? "transparent" : "var(--cream)",
            backgroundImage: pendingURL ? "none" : "repeating-linear-gradient(45deg, transparent 0 9px, rgba(45,67,41,0.04) 9px 10px)",
            padding: pendingURL ? 0 : "44px 20px",
            textAlign: "center",
            minHeight: pendingURL ? 240 : "auto",
            overflow: "hidden",
            position: "relative",
            transition: "border-color 200ms var(--ease)",
          }}
        >
          {pendingURL ? (
            <>
              <img src={pendingURL} alt="Aperçu" style={{ display: "block", maxWidth: "100%", maxHeight: 320, margin: "0 auto" }} />
              <button
                onClick={(e) => { e.stopPropagation(); setPendingURL(null); }}
                title="Retirer"
                style={{ position: "absolute", top: 8, right: 8, width: 28, height: 28, background: "rgba(28,36,27,0.7)", color: "#f1ead9", border: 0, borderRadius: "50%", cursor: "pointer", fontSize: 15 }}
              >×</button>
            </>
          ) : (
            <div style={{ color: "var(--ink-mute)" }}>
              <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" style={{ marginBottom: 10 }}>
                <rect x="3" y="3" width="18" height="18" rx="2"/>
                <circle cx="8.5" cy="8.5" r="1.5"/>
                <path d="m21 15-5-5L5 21"/>
              </svg>
              <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".14em", textTransform: "uppercase" }}>
                {busy ? "Traitement…" : "Glisser ou cliquer"}
              </div>
              <div style={{ fontFamily: "var(--serif)", fontSize: 13, color: "var(--ink-mute)", marginTop: 8, fontStyle: "italic" }}>
                PNG, JPEG, WebP — max 8 Mo
              </div>
            </div>
          )}
        </div>

        {/* Sélecteur de slot (uniquement après upload) */}
        {pendingURL && (
          <>
            <div style={{ marginTop: 22 }}>
              <div className="kicker" style={{ marginBottom: 10, color: "var(--moss-deep)" }}>Slot de destination</div>
              <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8 }}>
                {slots.map(s => {
                  const occupied = !!photos[s.id];
                  const selected = pendingSlot === s.id;
                  return (
                    <button
                      key={s.id}
                      type="button"
                      onClick={() => setPendingSlot(s.id)}
                      title={s.label + (occupied ? " (occupé — sera remplacé)" : " (libre)")}
                      style={{
                        padding: "10px 8px",
                        background: selected ? "var(--moss-deep)" : (occupied ? "var(--paper-2)" : "var(--cream)"),
                        color: selected ? "var(--paper)" : "var(--ink-soft)",
                        border: "1px solid " + (selected ? "var(--moss-deep)" : "var(--line)"),
                        fontFamily: "var(--mono)",
                        fontSize: 10,
                        letterSpacing: ".10em",
                        textTransform: "uppercase",
                        textAlign: "left",
                        cursor: "pointer",
                        transition: "all 180ms var(--ease)",
                      }}
                    >
                      <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 2 }}>
                        <span style={{ fontWeight: 700 }}>{s.id.toUpperCase()}</span>
                        {occupied && <span style={{ fontSize: 9, opacity: 0.7 }}>● occupé</span>}
                      </div>
                      <div style={{ fontSize: 9.5, opacity: 0.85, lineHeight: 1.3 }}>{s.label}</div>
                    </button>
                  );
                })}
              </div>
              {pendingSlot && photos[pendingSlot] && (
                <p style={{ fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 13, color: "var(--rust)", marginTop: 10, marginBottom: 0 }}>
                  ⚠ Le slot {pendingSlot.toUpperCase()} contient déjà une photo. Elle sera remplacée.
                </p>
              )}
            </div>

            <label style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 18, cursor: "pointer", fontFamily: "var(--serif)", fontSize: 15, color: "var(--ink-soft)" }}>
              <input
                type="checkbox"
                checked={makeDefault}
                onChange={e => setMakeDefault(e.target.checked)}
                style={{ width: 16, height: 16, cursor: "pointer" }}
              />
              ★ Définir comme photo principale
            </label>
          </>
        )}

        {error && (
          <div style={{ color: "var(--rust)", fontFamily: "var(--mono)", fontSize: 12, marginTop: 12 }}>{error}</div>
        )}
        {pendingNotice && (
          <div style={{
            marginTop: 14, padding: "12px 16px",
            background: "#fef3c7", borderLeft: "3px solid #dc6803",
            fontFamily: "var(--serif)", fontSize: 14, lineHeight: 1.5, color: "#7a2e0e",
          }}>
            <strong style={{ fontWeight: 500 }}>✓ {pendingNotice}</strong>
          </div>
        )}

        <div style={{ display: "flex", gap: 10, justifyContent: "flex-end", marginTop: 24 }}>
          <button className="btn" onClick={onClose} disabled={busy}>Annuler</button>
          <button
            className="btn btn-solid"
            onClick={handleSave}
            disabled={!pendingURL || !pendingSlot || busy || loading}
          >
            {busy ? "Enregistrement…" : "Ajouter la photo"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Section "Au jardin" — affichée quand p.arbre existe (fiches arboricoles).
// Pattern : sous-objet optionnel comme medicinal / comestibilite, structure
// horticole (périodes, besoins, taille, maladies, compagnes, utilisations,
// particularités). Chaque sous-bloc est lui-même optionnel et ne s'affiche
// que s'il contient au moins une valeur. Voir CLAUDE.md du projet pour le
// schéma complet attendu.
// ─────────────────────────────────────────────────────────────────────────────

function JardinSubBlock({ title, entries, note, isMobile }) {
  const visible = entries.filter(([, v]) => !!v);
  if (visible.length === 0) return null;
  return (
    <div style={{ marginTop: 32 }}>
      <div className="kicker" style={{ marginBottom: 14, color: "var(--moss-deep)" }}>{title}</div>
      <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: isMobile ? 14 : 22, rowGap: isMobile ? 14 : 18 }}>
        {visible.map(([label, value]) => (
          <div key={label}>
            <div style={{ fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".14em", color: "var(--ink-mute)", textTransform: "uppercase", marginBottom: 5 }}>{label}</div>
            <div style={{ fontFamily: "var(--serif)", fontSize: 16, lineHeight: 1.55, color: "var(--ink-soft)" }}>{value}</div>
          </div>
        ))}
      </div>
      {note && (
        <p style={{ fontFamily: "var(--serif)", fontSize: 15, lineHeight: 1.6, color: "var(--ink-soft)", fontStyle: "italic", marginTop: 16, paddingLeft: 14, borderLeft: "2px solid var(--line)" }}>
          <strong style={{ fontStyle: "normal", fontWeight: 500, color: "var(--ink)" }}>Recommandation —</strong> {note}
        </p>
      )}
    </div>
  );
}

function JardinList({ title, items }) {
  const valid = (items || []).filter(Boolean);
  if (valid.length === 0) return null;
  return (
    <div style={{ marginTop: 32 }}>
      <div className="kicker" style={{ marginBottom: 14, color: "var(--moss-deep)" }}>{title}</div>
      <ul style={{ paddingLeft: 0, listStyle: "none", margin: 0 }}>
        {valid.map((entry, i) => (
          <li key={i} style={{ fontFamily: "var(--serif)", fontSize: 16, lineHeight: 1.6, color: "var(--ink-soft)", paddingLeft: 22, position: "relative", marginBottom: 8 }}>
            <span style={{ position: "absolute", left: 0, top: "0.55em", width: 10, height: 1, background: "var(--moss-deep)" }} />
            {entry}
          </li>
        ))}
      </ul>
    </div>
  );
}

function JardinSection({ id, arbre, num, isMobile }) {
  const a = arbre || {};
  const hasPeriodes      = a.periodes && Object.values(a.periodes).some(Boolean);
  const hasBesoins       = a.besoins && Object.values(a.besoins).some(Boolean);
  const hasTaille        = a.taille && (a.taille.formation || a.taille.entretien || a.taille.fructification || a.taille.recommandation);
  const hasMaladies      = Array.isArray(a.maladies) && a.maladies.length > 0;
  const hasCompagnes     = a.compagnes && (a.compagnes.favorables || a.compagnes.aEviter);
  const hasUtilisations  = a.utilisations && Object.values(a.utilisations).some(Boolean);
  const hasParticularites= Array.isArray(a.particularites) && a.particularites.length > 0;

  return (
    <article id={id} style={fSection}>
      <Reveal>
        <SectionTitle num={num} title="Au jardin" isMobile={isMobile} />

        {/* Description générale + pH idéal */}
        {(a.description || a.phIdeal) && (
          <div style={{ marginBottom: 8 }}>
            {a.description && <p style={fProse}>{a.description}</p>}
            {a.phIdeal && (
              <div style={{ marginTop: 12 }}>
                <span className="tag"><span className="tag-dot" /> pH idéal : {a.phIdeal}</span>
              </div>
            )}
          </div>
        )}

        {/* Périodes importantes */}
        {hasPeriodes && (
          <JardinSubBlock
            title="Périodes importantes"
            entries={[
              ["Taille",         a.periodes.taille],
              ["Floraison",      a.periodes.floraison],
              ["Fructification", a.periodes.fructification],
              ["Plantation",     a.periodes.plantation],
            ]}
            isMobile={isMobile}
          />
        )}

        {/* Besoins et emplacement */}
        {hasBesoins && (
          <JardinSubBlock
            title="Besoins et emplacement"
            entries={[
              ["Emplacement", a.besoins.emplacement],
              ["Sol",         a.besoins.sol],
              ["Eau",         a.besoins.hydriques],
              ["Froid",       a.besoins.froid],
            ]}
            isMobile={isMobile}
          />
        )}

        {/* Type de taille */}
        {hasTaille && (
          <JardinSubBlock
            title="Type de taille"
            entries={[
              ["Formation",      a.taille.formation],
              ["Entretien",      a.taille.entretien],
              ["Fructification", a.taille.fructification],
            ]}
            note={a.taille.recommandation}
            isMobile={isMobile}
          />
        )}

        {/* Maladies & traitements bio */}
        {hasMaladies && <JardinList title="Maladies & traitements bio" items={a.maladies} />}

        {/* Plantes compagnes */}
        {hasCompagnes && (
          <div style={{ marginTop: 32 }}>
            <div className="kicker" style={{ marginBottom: 14, color: "var(--moss-deep)" }}>Plantes compagnes</div>
            <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: isMobile ? 14 : 22 }}>
              {a.compagnes.favorables && (
                <div>
                  <div style={{ fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".14em", color: "var(--olive)", textTransform: "uppercase", marginBottom: 6 }}>Favorables</div>
                  <p style={{ fontFamily: "var(--serif)", fontSize: 16, lineHeight: 1.55, color: "var(--ink-soft)", whiteSpace: "pre-line", margin: 0 }}>{a.compagnes.favorables}</p>
                </div>
              )}
              {a.compagnes.aEviter && (
                <div>
                  <div style={{ fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".14em", color: "var(--rust)", textTransform: "uppercase", marginBottom: 6 }}>À éviter</div>
                  <p style={{ fontFamily: "var(--serif)", fontSize: 16, lineHeight: 1.55, color: "var(--ink-soft)", whiteSpace: "pre-line", margin: 0 }}>{a.compagnes.aEviter}</p>
                </div>
              )}
            </div>
          </div>
        )}

        {/* Utilisations */}
        {hasUtilisations && (
          <JardinSubBlock
            title="Utilisations"
            entries={[
              ["Ornement", a.utilisations.ornement],
              ["Écologie", a.utilisations.ecologie],
              ["Bois",     a.utilisations.bois],
              ["Autre",    a.utilisations.autre],
            ]}
            isMobile={isMobile}
          />
        )}

        {/* Particularités */}
        {hasParticularites && <JardinList title="Particularités" items={a.particularites} />}
      </Reveal>
    </article>
  );
}

Object.assign(window, { Fiche });
