// LogoKodama : conteneur hote du composant web <kodama-logo>, la suite de logos
// « pixel terminal » V7.1 de KODAMA.
//
// SOURCE UNIQUE : brand/logos/ du depot parent kodama-apps (README.md pour les
// decisions L1 a L13, INVENTAIRE.md pour l etat des applications). Le fichier
// assets/kodama-logo.js est une COPIE posee par brand/logos/scripts/distribuer.mjs :
// on ne le modifie jamais ici (L2). Ce fichier-ci est le jumeau JSX (React 18,
// Babel dans le navigateur) du components/brand/LogoKodama.tsx des applis Next ;
// une correction se fait dans toutes les copies, a l identique.
//
// - L9  : le script du composant est charge par le <head> du shell ; ce conteneur
//         reserve la place du logo avant que l element soit defini, puis la mesure.
// - L11 : il rend TOUJOURS <kodama-logo scale="3"> (police de 24 px) et reduit par
//         CSS transform: scale(s/3). Une police de 2,7 px peut etre refusee
//         (taille minimale de police de Chrome), gonflee ou arrondie ; pas 24 px.
// - L13 : le composant ne sait pas jouer une fois, il boucle. Le mode « frappe »
//         pose animated puis le retire (pas + 10) x 70 ms plus tard, pendant la
//         pause qui suit la frappe complete : le logo se fige entier.
// - L7  : prefers-reduced-motion: reduce impose le mode fixe. animated est pose
//         par setAttribute, jamais rendu en JSX (le composant teste hasAttribute).
//
// Props : app, theme ("light" | "dark"), scale (au sens du designer, 1 = 115 px de
// large), label ("none" masque la ligne « > label »), mode ("fixe" | "boucle" |
// "frappe" | "survol"), cle (tout changement relance la frappe), relance (secondes,
// 0 = jamais), boite ({ largeur, hauteur }, reserve initiale), style (mise en
// page du conteneur : marges, jamais une taille).

const LOGO_SCALE_RENDU = 3;
const LOGO_PAS_MS = 70;
const LOGO_MARGE_PAS = 10;

// Geometrie des cartes de pixels de kodama-logo.js : rangees, colonnes, corps de
// police a scale 1 (8 partout, 4 pour le globe). Reserve initiale : largeur ~
// colonnes x 1,2 x fs x scale, hauteur = rangees x fs x scale (+ 19 x scale avec label).
const LOGO_GEOMETRIE = {
  cabane: { rangees: 11, colonnes: 12, fs: 8 },
  notes: { rangees: 12, colonnes: 12, fs: 8 },
  prod: { rangees: 12, colonnes: 12, fs: 8 },
  tests: { rangees: 10, colonnes: 12, fs: 8 },
  export: { rangees: 25, colonnes: 25, fs: 4 },
};

// Pas de frappe par famille (L13) : pixels non vides de la carte, divises par
// `step` pour le globe (5), arrondis au superieur. Recompte a chaque nouvel export
// du composant. Verifie par comptage le 2026-09-03 sur la source V7.1.
const LOGO_PAS = { cabane: 86, prod: 84, export: 89, tests: 84, notes: 130 };

function logoReserve(app, scale, avecLabel) {
  const g = LOGO_GEOMETRIE[app] || LOGO_GEOMETRIE.prod;
  return { largeur: g.colonnes * 1.2 * g.fs * scale, hauteur: g.rangees * g.fs * scale + (avecLabel ? 19 * scale : 0) };
}

function LogoKodama({ app, theme = "light", scale = 1, label, mode = "fixe", cle, relance = 0, boite, style }) {
  const conteneurRef = React.useRef(null);
  const logoRef = React.useRef(null);
  const facteur = scale / LOGO_SCALE_RENDU;
  const [taille, setTaille] = React.useState(() => boite || logoReserve(app, scale, label !== "none"));

  // La mesure : une fois defini, le composant a une taille propre (a scale 3) ;
  // le conteneur prend cette taille multipliee par le facteur. Tant que l element
  // n est pas defini il mesure 0 x 0 : on garde la reserve.
  React.useEffect(() => {
    const logo = logoRef.current;
    if (!logo || typeof ResizeObserver === "undefined") return;
    const observateur = new ResizeObserver((entrees) => {
      const b = entrees[0] && entrees[0].borderBoxSize && entrees[0].borderBoxSize[0];
      const largeur = b ? b.inlineSize : logo.offsetWidth;
      const hauteur = b ? b.blockSize : logo.offsetHeight;
      if (largeur > 0 && hauteur > 0) setTaille({ largeur: largeur * facteur, hauteur: hauteur * facteur });
    });
    observateur.observe(logo);
    return () => observateur.disconnect();
  }, [facteur]);

  // Le mouvement : pose et retrait de animated selon le mode (L13), le survol, la
  // relance, la cle et la preference de mouvement reduit (L7).
  React.useEffect(() => {
    const logo = logoRef.current;
    const conteneur = conteneurRef.current;
    if (!logo || !conteneur) return;
    const mouvementReduit = window.matchMedia("(prefers-reduced-motion: reduce)");
    const survolPossible = window.matchMedia("(hover: hover)").matches;
    let retrait;
    let minuteur;
    let demonte = false;
    const modeEffectif = () => (mouvementReduit.matches ? "fixe" : mode);
    const poser = () => logo.setAttribute("animated", "");
    const retirer = () => logo.removeAttribute("animated");
    const annulerRetrait = () => { if (retrait !== undefined) clearTimeout(retrait); retrait = undefined; };
    // Une frappe repart toujours de zero : le composant relance son horloge a
    // chaque changement d attribut, donc on retire avant de poser.
    const frapper = () => {
      annulerRetrait();
      retirer();
      poser();
      retrait = setTimeout(retirer, ((LOGO_PAS[app] || 90) + LOGO_MARGE_PAS) * LOGO_PAS_MS);
    };
    const appliquer = () => {
      annulerRetrait();
      const m = modeEffectif();
      if (m === "boucle") poser();
      else if (m === "frappe") frapper();
      else retirer();
    };
    // La premiere frappe attend que l element soit defini, sinon le compte a
    // rebours du retrait court pendant le chargement du script.
    const definition = typeof customElements !== "undefined" ? customElements.whenDefined("kodama-logo") : Promise.resolve();
    definition.then(() => { if (!demonte) appliquer(); });
    const surEntree = () => { const m = modeEffectif(); if (m === "frappe") frapper(); else if (m === "survol") poser(); };
    const surSortie = () => { if (modeEffectif() === "survol") retirer(); };
    if (survolPossible) {
      conteneur.addEventListener("pointerenter", surEntree);
      conteneur.addEventListener("pointerleave", surSortie);
    }
    if (relance > 0 && mode === "frappe") {
      minuteur = setInterval(() => { if (document.visibilityState === "visible" && modeEffectif() === "frappe") frapper(); }, relance * 1000);
    }
    mouvementReduit.addEventListener("change", appliquer);
    return () => {
      demonte = true;
      annulerRetrait();
      if (minuteur !== undefined) clearInterval(minuteur);
      mouvementReduit.removeEventListener("change", appliquer);
      if (survolPossible) {
        conteneur.removeEventListener("pointerenter", surEntree);
        conteneur.removeEventListener("pointerleave", surSortie);
      }
      retirer();
    };
  }, [app, mode, cle, relance]);

  // overflow VISIBLE, et c est voulu : le halo des pixels (text-shadow) deborde de
  // la boite. Le debordement de mise en page de l element rendu a scale 3 est sans
  // effet, ses bornes transformees sont celles de la boite.
  return (
    <span
      ref={conteneurRef}
      aria-hidden="true"
      style={Object.assign({ display: "inline-block", overflow: "visible", width: taille.largeur, height: taille.hauteur, flexShrink: 0, verticalAlign: "middle", WebkitTextSizeAdjust: "100%" }, style || {})}
    >
      <kodama-logo
        ref={logoRef}
        app={app}
        theme={theme}
        scale={String(LOGO_SCALE_RENDU)}
        label={label}
        style={{ display: "block", width: "max-content", transform: `scale(${facteur})`, transformOrigin: "top left" }}
      />
    </span>
  );
}

Object.assign(window, { LogoKodama });
