// sv-components.jsx — shared building blocks for the editorial-fullbleed language.
/* global React */
const { useState, useEffect, useRef, useCallback } = React;

/* ------------------------------------------------------------------ */
/* SafeImg — lazy image with fade-in on load + anti-CLS placeholder.  */
/* ------------------------------------------------------------------ */
function SafeImg({ img, alt = '', className = '', imgStyle = {}, cover = false, eager = false, sizes }) {
  const [loaded, setLoaded] = useState(false);
  const imgRef = useRef(null);
  // Cache-safe : si l'image est déjà complete au mount, onLoad ne refire pas.
  useEffect(() => {
    const el = imgRef.current;
    if (el && el.complete && el.naturalWidth > 0) setLoaded(true);
  }, []);
  // anti-CLS: reserve height via padding-bottom = aspect * 100%
  const padBottom = `${(img.aspect * 100).toFixed(2)}%`;
  return (
    <div
      className={`safe-img-wrap ${className}`}
      style={cover ? undefined : { paddingBottom: padBottom }}
    >
      <img
        ref={imgRef}
        className={`safe-img ${loaded ? '' : 'opacity-off'}`}
        src={img.src}
        srcSet={img.srcset}
        sizes={sizes || img.sizes}
        alt={alt}
        loading={eager ? 'eager' : 'lazy'}
        decoding="async"
        onLoad={() => setLoaded(true)}
        style={
          cover
            ? { width: '100%', height: '100%', objectFit: 'cover', ...imgStyle }
            : { position: 'absolute', inset: 0, width: '100%', height: '100%', ...imgStyle }
        }
      />
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* PillButton — 3-state interactive pill (repos / hovered / selected) */
/* ------------------------------------------------------------------ */
function PillButton({ children, selected = false, onClick, label = false, className = '', title }) {
  const [hovered, setHovered] = useState(false);
  const cls = [
    'pill', 'pill-btn',
    label ? 'pill-label' : '',
    hovered ? 'hovered' : '',
    selected ? 'selected' : '',
    className,
  ].filter(Boolean).join(' ');
  return (
    <button
      type="button"
      className={cls}
      title={title}
      onClick={onClick}
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
    >
      {children}
    </button>
  );
}

/* ------------------------------------------------------------------ */
/* CaptionPill — static legend anchored inside a block.               */
/* ------------------------------------------------------------------ */
function CaptionPill({ children, className = '' }) {
  return (
    <div className={`caption-pill-wrap ${className}`}>
      <span className="pill">{children}</span>
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* useReveal — IntersectionObserver entrance for blocks.              */
/* ------------------------------------------------------------------ */
function useReveal() {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (typeof IntersectionObserver === 'undefined') { setShown(true); return; }
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) { setShown(true); io.disconnect(); }
        });
      },
      { rootMargin: '0px 0px -10% 0px', threshold: 0.05 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, shown];
}

/* ------------------------------------------------------------------ */
/* BlockFullscreen — edge-to-edge block, whole block clickable.       */
/* ------------------------------------------------------------------ */
function BlockFullscreen({ item, onOpen, eager = false }) {
  // NOTE: pas de masquage du bloc via IntersectionObserver — un ancêtre en
  // opacity:0 empêche Chrome de fetcher les <img loading="lazy"> (deadlock
  // constaté en preview). Le fade-in par image au chargement suffit.
  const img = item.img;
  const landscape = img.isLandscape;
  const cls = ['block', 'block-fs', landscape ? 'is-landscape' : 'is-portrait'].join(' ');
  return (
    <div
      className={cls}
      onClick={() => onOpen(item.serieId)}
      role="link"
      tabIndex={0}
      onKeyDown={(e) => { if (e.key === 'Enter') onOpen(item.serieId); }}
    >
      <SafeImg img={img} alt={item.caption} cover={landscape} eager={eager} />
      {/* Pas de titre superposé en permanence (décision 24/07) — le label de
          la série n'apparaît qu'au survol, au pointeur fin (CSS : @media
          (hover:hover)), sans aucun fond. Rien au tactile.
          Le span interne est `sticky` : les blocs portrait mesurent ~141vh,
          un label collé au bas du bloc serait hors écran au survol. */}
      <div className="block-hover-label" aria-hidden="true">
        <span>{item.label}</span>
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* BlockPair — deux visuels partagent l'écran, colonnes jointives     */
/* (sans gouttière), constant à tous les breakpoints.                 */
/* ------------------------------------------------------------------ */
function BlockPair({ item, onOpen, eager = false }) {
  return (
    <div
      className="block block-pair"
      onClick={() => onOpen(item.serieId)}
      role="link"
      tabIndex={0}
      onKeyDown={(e) => { if (e.key === 'Enter') onOpen(item.serieId); }}
    >
      {item.imgs.map((img) => (
        <SafeImg key={img.base} img={img} alt={item.caption} cover eager={eager} sizes="50vw" />
      ))}
    </div>
  );
}

/* BlockFramed supprimé (mode « image sur fond flou » retiré — décision 24/07,
   rendu jugé mauvais avec ces photos). Tous les blocs passent en fullscreen. */

/* ------------------------------------------------------------------ */
/* Header — burger (left) + centred wordmark, legible over photos.    */
/* ------------------------------------------------------------------ */
function Header({ menuOpen, onToggleMenu, onWordmark }) {
  return (
    <header className="sv-header">
      <button
        type="button"
        className={`sv-burger sv-legible ${menuOpen ? 'is-open' : ''}`}
        aria-label={menuOpen ? 'Close menu' : 'Open menu'}
        aria-expanded={menuOpen}
        onClick={onToggleMenu}
      >
        <span /><span /><span />
      </button>
      <button
        type="button"
        className="sv-wordmark sv-legible"
        onClick={onWordmark}
        aria-label="Justin Personnaz — home"
      >
        Justin Personnaz
      </button>
    </header>
  );
}

/* ------------------------------------------------------------------ */
/* MenuOverlay — full-screen navigation.                              */
/* ------------------------------------------------------------------ */
function MenuOverlay({ open, t, lang, links, view, onNav, onToggleLang }) {
  const items = [
    { key: 'featured', label: t.menu.featured },
    { key: 'series', label: t.menu.series },
    { key: 'blog', label: t.menu.blog },
    { key: 'info', label: t.menu.info },
  ];
  // Un article lu (vue 'post') garde l'entrée Blog active.
  const isActive = (key) => view === key || (key === 'blog' && view === 'post');
  return (
    <nav className={`menu-overlay ${open ? 'is-open' : ''}`} aria-hidden={!open}>
      <div className="menu-ghost" aria-hidden="true">JP</div>
      <div className="menu-links">
        {items.map((it) => (
          <button
            key={it.key}
            type="button"
            className={`menu-link ${isActive(it.key) ? 'active' : ''}`}
            onClick={() => onNav(it.key)}
            tabIndex={open ? 0 : -1}
          >
            {it.label}
          </button>
        ))}
        <button
          type="button"
          className="menu-lang"
          onClick={onToggleLang}
          tabIndex={open ? 0 : -1}
        >
          {t.menu.lang}
        </button>
      </div>
      <div className="menu-foot">
        <a href={`mailto:${links.email}`} tabIndex={open ? 0 : -1}>{links.email}</a>
        <a href={links.instagram} target="_blank" rel="noopener noreferrer" tabIndex={open ? 0 : -1}>Instagram</a>
        <a href={links.linkedin} target="_blank" rel="noopener noreferrer" tabIndex={open ? 0 : -1}>LinkedIn</a>
      </div>
    </nav>
  );
}

/* Expose to global scope for Babel-in-browser (no modules). */
window.SafeImg = SafeImg;
window.PillButton = PillButton;
window.CaptionPill = CaptionPill;
window.useReveal = useReveal;
window.BlockFullscreen = BlockFullscreen;
window.BlockPair = BlockPair;
window.Header = Header;
window.MenuOverlay = MenuOverlay;
