/* ============================================================
   landing.jsx — l'écran d'entrée (accueil), en tant que composant
   AUTONOME exposé sur window.Landing.
   ------------------------------------------------------------
   Extrait de app.jsx (2026-07) pour pouvoir être pré-rendu côté
   Node par atelier/scripts/prerender/prerender-landing.mjs — le
   MÊME fichier source est exécuté et transpilé côté serveur (au
   build) et côté navigateur (Babel standalone, comme avant) :
   zéro divergence possible entre le HTML statique et ce que React
   affiche au premier rendu client, condition d'une hydratation
   sans erreur (ReactDOM.hydrateRoot).

   ⚠ RÈGLE ABSOLUE — hydratation
   Ce composant ne lit JAMAIS localStorage / window.location / la
   date dans son corps de rendu. Tout ce qui dépend du navigateur
   (prénom du jour, nombre de fiches du manifeste) est chargé dans
   un useEffect et part d'un état initial FIXE, identique au HTML
   pré-rendu. C'est pourquoi la carte « fiche du jour » et le
   compteur du dictionnaire ont chacun une valeur de repli écrite
   dans landing-content.js : c'est elle qui est pré-rendue, puis
   remplacée au montage.

   Refonte illustrée 2026-08 — structure rendue ici :
     1. héros 2 colonnes : texte + champ prénom + gravure
     2. trois portes hautes, gravure + sommaire de 3 lignes
     3. triptyque au format des cartes du blog (fiche du jour ·
        dernier article · rappels de fêtes), cliquables en entier
     4. bandeau dictionnaire, cliquable en entier
     5. frise des 26 lettrines gravées
     6. manifeste

   Le texte vient de window.LANDING_CONTENT (landing-content.js),
   seule source — voir ce fichier pour tout changement de copie.
   ============================================================ */
const LANDING_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");

function landingDaySlug(s) {
  return String(s || "")
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/(^-|-$)/g, "");
}

/* Nombre de fiches réellement publiées, arrondi à la centaine
   INFÉRIEURE pour que l'annonce reste vraie entre deux builds.
   Jamais écrit en dur : Seb ajoute des fiches par milliers. */
function landingFicheCount() {
  const slugs = window.FICHE_SLUGS;
  if (!slugs) return null;
  const n = Object.keys(slugs).length;
  if (n < 100) return null;
  return Math.floor(n / 100) * 100;
}

function landingFormatCount(n) {
  return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}

/* Accroche de la carte « fiche du jour » : la PREMIÈRE PHRASE de la
   notice étymologique (window.PRENOMS[…].s), qui est un paragraphe
   entier — on n'en garde qu'une phrase complète, jamais une coupe au
   milieu. Si elle est trop longue ou absente, on retombe sur l'origine,
   puis sur la phrase générique de landing-content.js. */
function landingDaySens(prenom, fallback) {
  const P = window.PRENOMS;
  if (!P) return fallback;
  const key = String(prenom || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
  const f = P[key];
  if (!f) return fallback;
  const s = String(f.s || "").trim();
  const m = s.match(/^[^.]{20,150}\./);
  if (m) return m[0];
  if (f.o) return "Origine " + f.o + ".";
  return fallback;
}

/* ------------------------------------------------------------
   Une carte du triptyque, au gabarit des cartes d'articles du
   blog : bandeau à lettrine typographique, puis corps de texte.
   `as` vaut "a" (navigation) ou "button" (ouvre les rappels).
   ------------------------------------------------------------ */
function LandingCard({ as, href, onClick, glyph, kicker, title, desc, meta, go }) {
  const inner = (
    <React.Fragment>
      <div className="landing-card-art"><span className="landing-card-glyph">{glyph}</span></div>
      <div className="landing-card-body">
        <p className="landing-card-k">{kicker}</p>
        <h3 className="landing-card-t">{title}</h3>
        <p className="landing-card-d">{desc}</p>
        {meta && meta.length > 0 &&
        <p className="landing-card-meta">
          {meta.map((m, i) =>
            <React.Fragment key={i}>
              {i > 0 && <span className="dot"></span>}
              <span>{m}</span>
            </React.Fragment>
          )}
        </p>}
        {go && <span className="landing-card-go">{go}</span>}
      </div>
    </React.Fragment>
  );
  if (as === "button") {
    return <button type="button" className="landing-card" onClick={onClick}>{inner}</button>;
  }
  return <a className="landing-card" href={href}>{inner}</a>;
}

/* ------------------------------------------------------------
   Triptyque. La fiche du jour est la seule partie dépendante de
   la date : état initial = les valeurs de repli du contenu, donc
   le premier rendu est identique au HTML pré-rendu.
   ------------------------------------------------------------ */
function LandingDayBar({ conf, onReminders }) {
  const [day, setDay] = React.useState(null);

  React.useEffect(() => {
    if (typeof window.prenomDuJour !== "function") return;
    const now = new Date();
    const info = window.prenomDuJour(now);
    if (!info || !info.prenom || info.isPrenom === false) return;
    const key = landingDaySlug(info.prenom);
    const ficheSlug = window.FICHE_SLUGS && window.FICHE_SLUGS[key];
    const initial = info.prenom.normalize("NFD").replace(/[̀-ͯ]/g, "").charAt(0).toUpperCase();
    setDay({
      prenom: info.prenom,
      sens: landingDaySens(info.prenom, ""),
      date: new Intl.DateTimeFormat("fr-FR", { day: "numeric", month: "long" }).format(now),
      href: ficheSlug ? "prenom/" + ficheSlug + ".html" : "prenom/du-jour",
      glyph: /^[A-Z]$/.test(initial) ? initial : conf.day.fallbackGlyph
    });
  }, []);

  return (
    <section className="landing-day-bar" aria-label="Fiche du jour, dernier article et rappels de fêtes">
      <LandingCard
        as="a"
        href={day ? day.href : conf.day.href}
        glyph={day ? day.glyph : conf.day.fallbackGlyph}
        kicker={conf.day.kicker}
        title={day ? day.prenom : conf.day.fallbackTitle}
        desc={day && day.sens ? day.sens : conf.day.fallbackDesc}
        meta={day ? ["Aujourd'hui · " + day.date] : null}
        go={conf.day.go}
      />
      <LandingCard
        as="a"
        href={conf.article.href}
        glyph={conf.article.glyph}
        kicker={conf.article.kicker}
        title={conf.article.title}
        desc={conf.article.desc}
        meta={conf.article.meta}
      />
      <LandingCard
        as="button"
        onClick={onReminders}
        glyph={conf.reminder.glyph}
        kicker={conf.reminder.kicker}
        title={conf.reminder.title}
        desc={conf.reminder.desc}
        go={conf.reminder.go}
      />
    </section>
  );
}

function Landing({ onChoose }) {
  const [remindersOpen, setRemindersOpen] = React.useState(false);
  // Dépliant des champs facultatifs du héros. État initial FIXE (false) :
  // c'est ce que le pré-rendu produit, donc l'hydratation reste identique.
  const [moreOpen, setMoreOpen] = React.useState(false);
  const [count, setCount] = React.useState(null);
  const C = window.LANDING_CONTENT || {};
  const hero = C.hero || {};
  const doors = C.doors || [];
  const dayBar = C.dayBar || null;
  const dico = C.dictionary || null;
  const alphabet = C.alphabet || null;

  // Le compteur dépend d'un fichier chargé par le navigateur :
  // il arrive après l'hydratation, jamais pendant le premier rendu.
  React.useEffect(() => { setCount(landingFicheCount()); }, []);

  function handleDoorClick(e, door) {
    if (door.kind !== "action") return; // laisse les vrais liens naviguer
    e.preventDefault();
    onChoose(door.key);
  }

  /* ------------------------------------------------------------
     Entrée par le champ du héros.

     ⚠ RÈGLE CANONIQUE DU SITE — une seule fiche par prénom.
     Quand un prénom SEUL est demandé, on ouvre sa FICHE (statique
     embarquée, ou autogénérée par le backend) au lieu de recalculer
     une lecture dans l'app : sinon le même prénom a deux versions,
     celle du dictionnaire (rédigée, avec portrait) et celle de
     l'app (calculée), et l'utilisateur tombe sur l'ancienne.
     C'est exactement ce que font déjà reveler() dans prenom.jsx et
     le paramètre ?prenom= dans app.jsx. Le héros doit s'y plier.

     La lecture in-app n'est utilisée QUE si l'internaute a rempli
     les autres prénoms ou le nom : la fiche du dictionnaire ne
     couvre pas ces deux axes (l'ombre et l'héritage), il faut donc
     bien calculer.
     ------------------------------------------------------------ */
  function landingSlug(s) {
    return String(s || "").normalize("NFD").replace(/[̀-ͯ]/g, "")
      .trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
  }

  function goPrenom(prenom, autres, nom) {
    const v = String(prenom || "").trim();
    if (!v) return; // rien à lire : on ne quitte pas l'accueil pour rien
    const a = String(autres || "").trim();
    const n = String(nom || "").trim();

    // Avec un nom → la page « nom complet » (/prenom-nom/<prenom>-<nom>),
    // qui traite l'accord prénom↔nom, le deuxième prénom s'il est donné
    // (?p2=), la sonorité de l'ensemble et le nombre du nom complet, et
    // qui renvoie vers les fiches finies de chacun des trois.
    // ⚠ Le handler coupe au PREMIER tiret : un prénom composé (Jean-Marc)
    // serait mal découpé. On ne route donc que si le prénom est simple.
    if (n) {
      const ps = landingSlug(v), ns = landingSlug(n);
      if (ps && ns && ps.indexOf("-") === -1) {
        const p2 = landingSlug(a.split(/[\s,]+/)[0] || "");
        window.location.href = "prenom-nom/" + ps + "-" + ns + (p2 ? "?p2=" + encodeURIComponent(p2) : "");
        return;
      }
    }

    // Deuxième prénom SANS nom de famille → la page « deux prénoms »
    // (/prenoms/<p1>-<p2>) : l'accord entre les deux, leur sonorité, leur
    // nombre, et un lien vers chacune des deux fiches.
    if (a && !n) {
      const ps2 = landingSlug(v), p2 = landingSlug(a.split(/[\s,]+/)[0] || "");
      if (ps2 && p2 && ps2.indexOf("-") === -1) { window.location.href = "prenoms/" + ps2 + "-" + p2; return; }
    }

    // Prénom seul → sa fiche du dictionnaire (statique ou autogénérée).
    if (!a && !n && typeof window.ficheHref === "function") {
      const href = window.ficheHref(v);
      if (href) { window.location.href = href; return; }
    }

    onChoose("prenom", { prenom: v, autres: a, nom: n });
  }

  // Les deux champs facultatifs ne sont montés QUE si le dépliant est
  // ouvert — d'où les lectures défensives sur e.target.elements.
  function submitHero(e) {
    e.preventDefault();
    const f = e.target.elements || {};
    const val = (n) => (f[n] && f[n].value ? f[n].value : "");
    goPrenom(val("heroPrenom"), val("heroAutres"), val("heroNom"));
  }
  function pickExample(name) { goPrenom(name); }

  return (
    <div className="fade-screen landing">

      {/* ---------- 1. héros ---------- */}
      <div className="landing-hero">
        <div className="landing-hero-copy">
          <div className="eyebrow">{C.eyebrow}</div>
          <h1 className="lede" dangerouslySetInnerHTML={{ __html: C.h1Html || "" }}></h1>
          {C.heroFactual && <p className="hero-factual">{C.heroFactual}</p>}
          <p className="sub">{C.sub}</p>
          <form className="landing-hero-form" onSubmit={submitHero}>
            <div className="landing-search">
              <input type="text" name="heroPrenom" autoComplete="off"
                placeholder={hero.placeholder} aria-label={hero.placeholder} />
              <span className="landing-search-sep"></span>
              <button className="btn-primary landing-search-cta" type="submit">{hero.cta}</button>
            </div>

            {/* Dépliant : le coût d'entrée reste d'un seul champ, mais le
                libellé annonce ce que les autres sites ne font pas. */}
            {hero.more &&
            <React.Fragment>
              <button type="button" className={"landing-more-toggle" + (moreOpen ? " is-open" : "")}
                aria-expanded={moreOpen} onClick={() => setMoreOpen(!moreOpen)}>
                <span className="landing-more-sign" aria-hidden="true">{moreOpen ? "−" : "+"}</span>
                <span className="landing-more-label">{moreOpen ? hero.more.close : hero.more.open}</span>
                {!moreOpen && <span className="landing-more-why">{hero.more.why}</span>}
              </button>

              {moreOpen &&
              <div className="landing-more">
                <div className="landing-more-field">
                  <label className="landing-more-lab" htmlFor="heroAutres">
                    {hero.more.autresLabel} <span className="opt">{hero.more.autresOpt}</span>
                  </label>
                  <input type="text" id="heroAutres" name="heroAutres" autoComplete="off"
                    placeholder={hero.more.autresPlaceholder} />
                  <p className="landing-more-hint">{hero.more.autresHint}</p>
                </div>
                <div className="landing-more-field">
                  <label className="landing-more-lab" htmlFor="heroNom">
                    {hero.more.nomLabel} <span className="opt">{hero.more.nomOpt}</span>
                  </label>
                  <input type="text" id="heroNom" name="heroNom" autoComplete="off"
                    placeholder={hero.more.nomPlaceholder} />
                  <p className="landing-more-hint">{hero.more.nomHint}</p>
                </div>
              </div>}
            </React.Fragment>}
          </form>

          {hero.examples && hero.examples.length > 0 &&
          <div className="landing-examples">
            <span>{hero.examplesLabel}</span>
            {hero.examples.map((n) =>
              <button type="button" key={n} onClick={() => pickExample(n)}>{n}</button>
            )}
          </div>}
        </div>
        {hero.art &&
        <div className="landing-hero-art">
          <img src={hero.art} alt={hero.artAlt || ""} width="720" height="962" />
        </div>}
      </div>

      {/* ---------- 2. les trois portes ---------- */}
      <nav className="doors">
        {doors.map((door) =>
          <a key={door.key} className={"door door-" + door.key} href={door.href}
            onClick={door.kind === "action" ? (e) => handleDoorClick(e, door) : undefined}>
            {door.art &&
            <span className="landing-door-art-zone">
              <img className="landing-door-art" src={door.art} alt="" loading="lazy" />
            </span>}
            <div className="door-k">{door.kicker}</div>
            <div className="door-t">{door.title}</div>
            <p className="door-d">{door.desc}</p>
            {door.bullets && door.bullets.length > 0 &&
            <ul className="landing-door-list">
              {door.bullets.map((b, i) => <li key={i}>{b}</li>)}
            </ul>}
            <span className="door-go">{door.go}</span>
          </a>
        )}
      </nav>

      {/* ---------- 3. triptyque ---------- */}
      {dayBar && <LandingDayBar conf={dayBar} onReminders={() => setRemindersOpen(true)} />}
      {remindersOpen && window.RappelsCard &&
        <div className="landing-reminder-form"><window.RappelsCard initialOpen={true} /></div>}

      {/* ---------- 4. bandeau dictionnaire ---------- */}
      {dico &&
      <section className="landing-dico">
        {dico.art && <img className="landing-dico-art" src={dico.art} alt="" loading="lazy" />}
        <a className="landing-dico-hit" href={dico.href} aria-label="Parcourir le dictionnaire — tous les prénoms"></a>
        <div className="landing-dico-in">
          <h2 className="landing-dico-title" dangerouslySetInnerHTML={{ __html: dico.titleHtml || "" }}></h2>
          <span className="landing-dico-enter">{dico.enter}</span>
          <p className="landing-block-blurb">{dico.text}</p>
          <div className="landing-chip-list">
            {dico.links.map((l, i) => <a key={l.href + i} className="landing-chip" href={l.href}>{l.label}</a>)}
          </div>
          <div className="landing-chip-list" style={{ marginTop: "12px" }}>
            {dico.origins.map((l) => <a key={l.href} className="landing-chip" href={l.href}>{l.label}</a>)}
          </div>
          {count && <p className="landing-fine">{dico.countTpl.replace("{n}", landingFormatCount(count))}</p>}
        </div>
      </section>}

      {/* ---------- 5. frise des vingt-six lettrines ---------- */}
      {alphabet &&
      <section className="landing-alphabet">
        <h2 className="landing-block-title" dangerouslySetInnerHTML={{ __html: alphabet.titleHtml || "" }}></h2>
        <p className="landing-block-blurb">{alphabet.blurb}</p>
        <div className="landing-frieze">
          {LANDING_ALPHABET.map((L) =>
            <a key={L} href={alphabet.hrefTpl.replace("{x}", L.toLowerCase())} aria-label={"Prénoms commençant par " + L}>
              <img src={alphabet.artTpl.replace("{X}", L)} alt={L} loading="lazy" />
            </a>
          )}
        </div>
      </section>}

      {/* ---------- 6. manifeste ---------- */}
      {C.manifesto && <p className="landing-manifesto">{C.manifesto}</p>}
    </div>);
}
window.Landing = Landing;
