// Lightbox — backdrop click-to-nav, looping, cursor variants, layout variants,
// fade in/out, slow-load spinner, adjacent-image preloading
const { useState: useStateL, useEffect: useEffectL, useRef: useRefL } = React;

const LB_EXIT_MS = 260;
const LB_SLOW_MS = 175; // show spinner only after this delay

// Delegate preloading + cache to the shared <Img> component so the
// gallery strip and the lightbox share one set of "already-loaded" URLs.
const LB_LOADED = { has: (src) => window.Img && window.Img.isLoaded(src) };
const lbPreload = (src) => window.Img && window.Img.preload(src);

// Pseudo-random but stable per index — used by stack/atlas variants for rotations
function pseudoRand(i, salt = 0) {
  const x = Math.sin(i * 9301 + salt * 49297) * 10000;
  return x - Math.floor(x);
}

function CollageMeta({ item, index, total, label }) {
  const lb = item.lightbox || {};
  const itemTitle = item.caption?.title || "";
  const images = (lb.images && lb.images.length ? lb.images : [{ src: item.src, caption: item.caption }]);
  const rawDisplay = (lb.display || lb.title || itemTitle || "").trim();
  const displayWord = (lb.display || rawDisplay.split(/[\s·\-]+/)[0] || "").toUpperCase();
  const kicker = (lb.kicker || (itemTitle || label)).toUpperCase();
  const intro = lb.intro || `Una colección que reúne momentos, escenarios y rostros capturados en distintas latitudes; el archivo crece y dialoga consigo mismo, dejando ver patrones, repeticiones y silencios que solo aparecen cuando las imágenes se miran en conjunto.`;
  const chapterNum = String(index + 1).padStart(2, "0");
  return { lb, itemTitle, images, displayWord, kicker, intro, chapterNum };
}

// Hook: scale a heading element so its single-line text fits parent width
function useFitToWidth(ref, dep) {
  React.useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;
    const fit = () => {
      const parent = el.parentElement;
      if (!parent) return;
      const cs = window.getComputedStyle(parent);
      const padL = parseFloat(cs.paddingLeft) || 0;
      const padR = parseFloat(cs.paddingRight) || 0;
      const available = parent.clientWidth - padL - padR;
      if (available <= 0) return;
      const SAMPLE = 100;
      el.style.fontSize = SAMPLE + "px";
      const w = el.scrollWidth;
      if (w <= 0) return;
      const next = Math.max(40, Math.min(420, (available / w) * SAMPLE * 0.98));
      el.style.fontSize = next + "px";
    };
    fit();
    const ro = new ResizeObserver(fit);
    ro.observe(el.parentElement);
    window.addEventListener("resize", fit);
    return () => {
      ro.disconnect();
      window.removeEventListener("resize", fit);
    };
  }, [dep]);
}

// VARIANT 1 — SPREAD (original): magazine-style 2-page editorial spread
function CollageSpread({ item, index, total, label }) {
  const { images, displayWord, kicker, intro, chapterNum } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");

  const displayRef = React.useRef(null);
  useFitToWidth(displayRef, displayWord);

  const cells = images.slice(0, 6);
  const heroIndex = cells.length >= 5 ? 3 : Math.min(0, cells.length - 1);

  return (
    <div className="lightbox__collage" data-variant="spread">
      <div className="lightbox__collage-page lightbox__collage-page--left">
        <p className="lightbox__collage-intro serif">{intro}</p>
        <div className="lightbox__collage-chapter">
          <span className="lightbox__collage-num">{chapterNum}</span>
          <span className="lightbox__collage-rule" aria-hidden="true" />
          <span className="lightbox__collage-kicker mono">{kicker}</span>
        </div>
        <h1 ref={displayRef} className="lightbox__collage-display serif">{displayWord}</h1>
        <footer className="lightbox__collage-foot mono">
          <span>{pad2(index + 1)}</span>
          <span>{(label || "").toUpperCase()}</span>
        </footer>
      </div>

      <div className="lightbox__collage-page lightbox__collage-page--right">
        <ol className="lightbox__collage-captions mono">
          {cells.map((c, k) => (
            <li key={k}>
              <span className="lightbox__collage-cap-num">{k + 1}.</span>
              <span className="lightbox__collage-cap-text">{c.caption?.title}</span>
            </li>
          ))}
        </ol>
        <div className="lightbox__collage-grid">
          {cells.map((c, k) => (
            <figure
              key={k}
              className={"lightbox__collage-cell" + (k === heroIndex ? " is-hero" : "") + " cell-" + k}
            >
              <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
              <figcaption className="mono">{c.caption?.title}</figcaption>
            </figure>
          ))}
        </div>
        <footer className="lightbox__collage-foot mono">
          <span>{(label || "").toUpperCase()}</span>
          <span>{pad2(index + 1)} / {pad2(total)}</span>
        </footer>
      </div>
    </div>
  );
}

// VARIANT 2 — MOSAIC: full-bleed asymmetric photo grid with floating editorial card
function CollageMosaic({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 7);

  return (
    <div className="lightbox__collage-mosaic">
      <div className="cmosaic__grid">
        {cells.map((c, k) => (
          <figure key={k} className={"cmosaic__cell cmosaic__cell--" + k}>
            <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
            <figcaption className="mono">
              <span>{pad2(k + 1)}</span>
              <span>{c.caption?.title}</span>
            </figcaption>
          </figure>
        ))}
      </div>

      <aside className="cmosaic__card">
        <div className="cmosaic__card-kicker mono">
          <span>{kicker}</span>
          <span>{pad2(index + 1)} / {pad2(total)}</span>
        </div>
        <h1 className="cmosaic__card-display serif">{displayWord}</h1>
        <p className="cmosaic__card-intro serif">{intro}</p>
        <div className="cmosaic__card-foot mono">
          <span>{(label || "").toUpperCase()}</span>
          <span>{cells.length} IMÁGENES</span>
        </div>
      </aside>
    </div>
  );
}

// VARIANT 3 — STACK: scrapbook polaroids fanned across a tinted board
function CollageStack({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 7);

  return (
    <div className="lightbox__collage-stack">
      <header className="cstack__head">
        <div className="cstack__head-kicker mono">
          <span className="cstack__dot" /> {kicker}
        </div>
        <h1 className="cstack__display serif">{displayWord}</h1>
        <p className="cstack__intro serif">{intro}</p>
        <div className="cstack__head-meta mono">
          <span>{pad2(index + 1)} / {pad2(total)}</span>
          <span>·</span>
          <span>{(label || "").toUpperCase()}</span>
          <span>·</span>
          <span>{cells.length} PIEZAS</span>
        </div>
      </header>

      <div className="cstack__board">
        {cells.map((c, k) => {
          const rot = (pseudoRand(index * 17 + k, 1) - 0.5) * 14;
          const ox = (pseudoRand(index * 17 + k, 2) - 0.5) * 18;
          const oy = (pseudoRand(index * 17 + k, 3) - 0.5) * 14;
          return (
            <figure
              key={k}
              className={"cstack__polaroid cstack__polaroid--" + k}
              style={{ "--rot": rot + "deg", "--ox": ox + "px", "--oy": oy + "px" }}
            >
              <span className="cstack__tape" aria-hidden="true" />
              <div className="cstack__photo">
                <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
              </div>
              <figcaption className="cstack__cap mono">
                <span>{pad2(k + 1)}</span>
                <span>{c.caption?.title}</span>
              </figcaption>
            </figure>
          );
        })}
      </div>
    </div>
  );
}

// VARIANT 4 — STRIP: contact-sheet filmstrip with sprocket holes
function CollageStrip({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 8);

  return (
    <div className="lightbox__collage-strip">
      <header className="cstrip__head">
        <div className="cstrip__head-row mono">
          <span className="cstrip__brand">MAPA · LÓPEZ</span>
          <span className="cstrip__divider" />
          <span>{kicker}</span>
          <span className="cstrip__spacer" />
          <span>{pad2(index + 1)} / {pad2(total)}</span>
        </div>
        <h1 className="cstrip__display serif">{displayWord}</h1>
        <p className="cstrip__intro serif">{intro}</p>
      </header>

      <div className="cstrip__film">
        <div className="cstrip__perf cstrip__perf--top" aria-hidden="true">
          {Array.from({ length: 28 }).map((_, k) => <span key={k} />)}
        </div>
        <div className="cstrip__frames">
          {cells.map((c, k) => (
            <figure key={k} className="cstrip__frame">
              <span className="cstrip__frame-num mono">{pad2(k + 1)}A</span>
              <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
              <figcaption className="mono">{c.caption?.title}</figcaption>
            </figure>
          ))}
        </div>
        <div className="cstrip__perf cstrip__perf--bot" aria-hidden="true">
          {Array.from({ length: 28 }).map((_, k) => <span key={k} />)}
        </div>
      </div>

      <footer className="cstrip__foot mono">
        <span>KODAK TRI-X 400</span>
        <span>·</span>
        <span>{(label || "").toUpperCase()}</span>
        <span>·</span>
        <span>{cells.length} EXP.</span>
      </footer>
    </div>
  );
}

// VARIANT 5 — ATLAS: archival index, specimen cards on grid paper
function CollageAtlas({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 6);

  return (
    <div className="lightbox__collage-atlas">
      <header className="catlas__head">
        <div className="catlas__head-l">
          <div className="catlas__kicker mono">
            <span>ARCHIVO</span>
            <span className="catlas__rule" />
            <span>{kicker}</span>
          </div>
          <h1 className="catlas__display serif">{displayWord}</h1>
        </div>
        <div className="catlas__head-r mono">
          <div className="catlas__meta-row"><span>FOLIO</span><span>{pad2(index + 1)}</span></div>
          <div className="catlas__meta-row"><span>SERIE</span><span>{(label || "").toUpperCase()}</span></div>
          <div className="catlas__meta-row"><span>PIEZAS</span><span>{pad2(cells.length)}</span></div>
          <div className="catlas__meta-row"><span>FECHA</span><span>2024</span></div>
        </div>
      </header>

      <p className="catlas__intro serif">{intro}</p>

      <div className="catlas__grid">
        {cells.map((c, k) => (
          <figure key={k} className="catlas__card">
            <div className="catlas__card-tag mono">
              <span>N° {pad2(k + 1)}</span>
              <span>·</span>
              <span>EJ. {String.fromCharCode(65 + k)}</span>
            </div>
            <div className="catlas__card-photo">
              <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
            </div>
            <figcaption className="catlas__card-cap">
              <div className="catlas__card-title serif">{c.caption?.title}</div>
              <div className="catlas__card-meta mono">
                {c.caption?.location ? <span>{c.caption.location}</span> : <span>—</span>}
                <span>·</span>
                <span>MLZ_{pad2(k + 1)}</span>
              </div>
            </figcaption>
          </figure>
        ))}
      </div>
    </div>
  );
}

// VARIANT 6 — GALLERY: art-gallery exhibition wall, single hero with placard
function CollageGallery({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 5);
  const hero = cells[0];
  const rest = cells.slice(1);

  const displayRef = React.useRef(null);
  useFitToWidth(displayRef, displayWord);

  return (
    <div className="lightbox__collage-gallery">
      <div className="cgal__wall">
        {hero && (
          <figure className="cgal__hero">
            <div className="cgal__hero-frame">
              <Img src={hero.src} alt={hero.caption?.title || ""} fallbackLabel={(hero.caption?.title || "").toUpperCase()} />
            </div>
            <figcaption className="cgal__placard">
              <div className="cgal__placard-num mono">N° {pad2(index + 1)}</div>
              <div className="cgal__placard-title serif">{hero.caption?.title}</div>
              <div className="cgal__placard-meta mono">
                <span>{hero.caption?.location || "—"}</span>
                <span>·</span>
                <span>IMPRESIÓN ARCHIVO</span>
              </div>
            </figcaption>
          </figure>
        )}

        <aside className="cgal__plates">
          {rest.map((c, k) => (
            <figure key={k} className="cgal__plate">
              <div className="cgal__plate-frame">
                <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
              </div>
              <figcaption className="mono">
                <span className="cgal__plate-num">{pad2(k + 2)}</span>
                <span>{c.caption?.title}</span>
              </figcaption>
            </figure>
          ))}
        </aside>
      </div>

      <header className="cgal__header">
        <div className="cgal__header-kicker mono">
          <span>EXPOSICIÓN</span>
          <span className="cgal__header-rule" />
          <span>{kicker}</span>
        </div>
        <h1 ref={displayRef} className="cgal__display serif">{displayWord}</h1>
        <p className="cgal__intro serif">{intro}</p>
        <footer className="cgal__foot mono">
          <span>SALA {String.fromCharCode(64 + ((index % 6) + 1))}</span>
          <span>·</span>
          <span>{(label || "").toUpperCase()}</span>
          <span>·</span>
          <span>{pad2(cells.length)} OBRAS</span>
        </footer>
      </header>
    </div>
  );
}

// VARIANT 7 — REVIEW: 3-column photo-review with running text and pull-quote
function CollageReview({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 5);

  // build a "review" body — split intro into several paragraphs and add a pull-quote
  const sentences = intro.split(/(?<=[\.!\?])\s+/).filter(Boolean);
  const para1 = sentences.slice(0, Math.ceil(sentences.length / 2)).join(" ");
  const para2 = sentences.slice(Math.ceil(sentences.length / 2)).join(" ");
  const pullQuote = (cells[0]?.caption?.title || displayWord).toUpperCase();

  return (
    <div className="lightbox__collage-review">
      <header className="crev__head">
        <div className="crev__head-kicker mono">
          <span>REVISTA · ENSAYO FOTOGRÁFICO</span>
          <span className="crev__head-spacer" />
          <span>{pad2(index + 1)} / {pad2(total)}</span>
        </div>
        <h1 className="crev__display serif">{displayWord}</h1>
        <div className="crev__byline mono">
          <span>POR MAPA LÓPEZ</span>
          <span>·</span>
          <span>{kicker}</span>
        </div>
      </header>

      <div className="crev__body">
        <div className="crev__col crev__col--1">
          <p className="crev__lead serif"><span className="crev__dropcap">{para1.charAt(0)}</span>{para1.slice(1)}</p>
          {cells[0] && (
            <figure className="crev__photo">
              <Img src={cells[0].src} alt={cells[0].caption?.title || ""} fallbackLabel={(cells[0].caption?.title || "").toUpperCase()} />
              <figcaption className="mono"><span>01</span> {cells[0].caption?.title}</figcaption>
            </figure>
          )}
        </div>

        <div className="crev__col crev__col--2">
          {cells[1] && (
            <figure className="crev__photo crev__photo--tall">
              <Img src={cells[1].src} alt={cells[1].caption?.title || ""} fallbackLabel={(cells[1].caption?.title || "").toUpperCase()} />
              <figcaption className="mono"><span>02</span> {cells[1].caption?.title}</figcaption>
            </figure>
          )}
          <blockquote className="crev__quote serif">
            <span className="crev__quote-mark">“</span>{pullQuote}<span className="crev__quote-mark">”</span>
          </blockquote>
          {cells[2] && (
            <figure className="crev__photo">
              <Img src={cells[2].src} alt={cells[2].caption?.title || ""} fallbackLabel={(cells[2].caption?.title || "").toUpperCase()} />
              <figcaption className="mono"><span>03</span> {cells[2].caption?.title}</figcaption>
            </figure>
          )}
        </div>

        <div className="crev__col crev__col--3">
          {cells[3] && (
            <figure className="crev__photo">
              <Img src={cells[3].src} alt={cells[3].caption?.title || ""} fallbackLabel={(cells[3].caption?.title || "").toUpperCase()} />
              <figcaption className="mono"><span>04</span> {cells[3].caption?.title}</figcaption>
            </figure>
          )}
          <p className="crev__para serif">{para2}</p>
          {cells[4] && (
            <figure className="crev__photo">
              <Img src={cells[4].src} alt={cells[4].caption?.title || ""} fallbackLabel={(cells[4].caption?.title || "").toUpperCase()} />
              <figcaption className="mono"><span>05</span> {cells[4].caption?.title}</figcaption>
            </figure>
          )}
        </div>
      </div>

      <footer className="crev__foot mono">
        <span>MAPA LÓPEZ — ARCHIVO {pad2(index + 1)}</span>
        <span>{(label || "").toUpperCase()}</span>
      </footer>
    </div>
  );
}

// VARIANT 8 — PORTFOLIO: asymmetric, big title (1/3) + varied photo grid (2/3)
function CollagePortfolio({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 6);

  const displayRef = React.useRef(null);
  useFitToWidth(displayRef, displayWord);

  return (
    <div className="lightbox__collage-portfolio">
      <aside className="cport__title">
        <div className="cport__title-top mono">
          <span>PORTAFOLIO</span>
          <span>{pad2(index + 1)} / {pad2(total)}</span>
        </div>
        <h1 ref={displayRef} className="cport__display serif">{displayWord}</h1>
        <div className="cport__kicker mono">{kicker}</div>
        <p className="cport__intro serif">{intro}</p>
        <ol className="cport__index mono">
          {cells.map((c, k) => (
            <li key={k}>
              <span>{pad2(k + 1)}</span>
              <span className="cport__index-rule" />
              <span>{c.caption?.title}</span>
            </li>
          ))}
        </ol>
        <div className="cport__title-foot mono">
          <span>MAPA LÓPEZ</span>
          <span>{(label || "").toUpperCase()}</span>
        </div>
      </aside>

      <div className="cport__grid">
        {cells.map((c, k) => (
          <figure key={k} className={"cport__cell cport__cell--" + k}>
            <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
            <figcaption className="mono">
              <span>{pad2(k + 1)}</span>
              <span>{c.caption?.title}</span>
            </figcaption>
          </figure>
        ))}
      </div>
    </div>
  );
}

// VARIANT 9 — JOURNAL: text page with marginalia + asymmetric staircase photo grid
function CollageJournal({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 5);

  return (
    <div className="lightbox__collage-journal">
      <article className="cjour__page">
        <div className="cjour__margin mono">
          <div className="cjour__margin-block">
            <span>{kicker}</span>
          </div>
          <div className="cjour__margin-block">
            <span>FOLIO</span>
            <span>{pad2(index + 1)}</span>
          </div>
          <div className="cjour__margin-block">
            <span>SERIE</span>
            <span>{(label || "").toUpperCase()}</span>
          </div>
          <div className="cjour__margin-block">
            <span>PIEZAS</span>
            <span>{pad2(cells.length)}</span>
          </div>
        </div>

        <div className="cjour__body">
          <div className="cjour__chapter mono">CAPÍTULO {pad2(index + 1)}</div>
          <h1 className="cjour__display serif">{displayWord}</h1>
          <div className="cjour__rule" />
          <p className="cjour__intro serif">{intro}</p>
          <ol className="cjour__list mono">
            {cells.map((c, k) => (
              <li key={k}>
                <span>{pad2(k + 1)}.</span>
                <span>{c.caption?.title}</span>
                <span className="cjour__list-loc">{c.caption?.location}</span>
              </li>
            ))}
          </ol>
        </div>
      </article>

      <div className="cjour__plates">
        {cells.map((c, k) => (
          <figure key={k} className={"cjour__plate cjour__plate--" + k}>
            <div className="cjour__plate-img">
              <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
            </div>
            <figcaption className="mono">
              <span>FIG. {pad2(k + 1)}</span>
              <span>{c.caption?.title}</span>
            </figcaption>
          </figure>
        ))}
      </div>
    </div>
  );
}

// VARIANT 10 — BROADSHEET: newspaper-style masthead + 4 column photo arrangement
function CollageBroadsheet({ item, index, total, label }) {
  const { images, displayWord, kicker, intro } = CollageMeta({ item, index, total, label });
  const pad2 = (v) => String(v).padStart(2, "0");
  const cells = images.slice(0, 6);

  return (
    <div className="lightbox__collage-broadsheet">
      <header className="cbs__masthead">
        <div className="cbs__masthead-l mono">
          <span>VOL. III</span>
          <span>·</span>
          <span>N° {pad2(index + 1)}</span>
        </div>
        <div className="cbs__masthead-c serif">MAPA · LÓPEZ</div>
        <div className="cbs__masthead-r mono">
          <span>{(label || "").toUpperCase()}</span>
          <span>·</span>
          <span>EDICIÓN ARCHIVO</span>
        </div>
      </header>

      <div className="cbs__heading">
        <div className="cbs__kicker mono">{kicker}</div>
        <h1 className="cbs__display serif">{displayWord}</h1>
        <p className="cbs__sub serif">{intro}</p>
      </div>

      <div className="cbs__grid">
        {cells.map((c, k) => (
          <figure key={k} className={"cbs__cell cbs__cell--" + k}>
            <div className="cbs__cell-img">
              <Img src={c.src} alt={c.caption?.title || ""} fallbackLabel={(c.caption?.title || "").toUpperCase()} />
            </div>
            <figcaption>
              <div className="cbs__cell-num mono">{pad2(k + 1)}</div>
              <div className="cbs__cell-title serif">{c.caption?.title}</div>
              {c.caption?.location && (
                <div className="cbs__cell-loc mono">{c.caption.location}</div>
              )}
            </figcaption>
          </figure>
        ))}
      </div>

      <footer className="cbs__foot mono">
        <span>IMPRESO EN MÉXICO</span>
        <span className="cbs__foot-rule" />
        <span>MLZ_{pad2(index + 1)}_{(label || "").replace(/\s/g, "")}</span>
      </footer>
    </div>
  );
}

// Dispatcher — picks variant based on item.lightbox.variant (data-driven).
// Defaults to "spread" if unset.
function CollageSpreadDispatcher(props) {
  const variant = (props.item && props.item.lightbox && props.item.lightbox.variant) || "spread";

  if (variant === "mosaic")    return <CollageMosaic {...props} />;
  if (variant === "stack")     return <CollageStack {...props} />;
  if (variant === "strip")     return <CollageStrip {...props} />;
  if (variant === "atlas")     return <CollageAtlas {...props} />;
  if (variant === "gallery")   return <CollageGallery {...props} />;
  if (variant === "review")    return <CollageReview {...props} />;
  if (variant === "portfolio") return <CollagePortfolio {...props} />;
  if (variant === "journal")   return <CollageJournal {...props} />;
  if (variant === "broadsheet")return <CollageBroadsheet {...props} />;
  return <CollageSpread {...props} />;
}

function Lightbox({ state, onClose }) {
  const [i, setI] = useStateL(0);
  const [cursorSide, setCursorSide] = useStateL(null); // 'prev' | 'next' | null
  const [cursorPos, setCursorPos] = useStateL({ x: -100, y: -100 });
  const [visible, setVisible] = useStateL(false);
  const [snap, setSnap] = useStateL(null); // retained state during exit
  const [loaded, setLoaded] = useStateL(false);
  const [slowLoad, setSlowLoad] = useStateL(false);
  const exitTimer = useRefL(null);
  const slowTimer = useRefL(null);

  // Open / close — keep mounted during exit fade
  useEffectL(() => {
    if (state) {
      clearTimeout(exitTimer.current);
      setSnap(state);
      setI(state.index);
      setVisible(false);
      // Aggressively preload every image in this gallery in the background.
      state.items.forEach((it) => lbPreload(it.src));
      // Double rAF so initial opacity:0 gets a paint before flipping to 1
      requestAnimationFrame(() => {
        requestAnimationFrame(() => setVisible(true));
      });
    } else if (snap) {
      setVisible(false);
      exitTimer.current = setTimeout(() => setSnap(null), LB_EXIT_MS);
    }
    return () => clearTimeout(exitTimer.current);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [state]);

  // When index or snap changes: reset load state, schedule slow-load spinner,
  // instantly mark loaded if image is already in our cache, and preload neighbours.
  useEffectL(() => {
    if (!snap) return;
    const src = snap.items[i].src;

    // Already loaded this session? Skip the loading state entirely.
    if (LB_LOADED.has(src)) {
      setLoaded(true);
      setSlowLoad(false);
      clearTimeout(slowTimer.current);
    } else {
      setLoaded(false);
      setSlowLoad(false);
      clearTimeout(slowTimer.current);
      slowTimer.current = setTimeout(() => setSlowLoad(true), LB_SLOW_MS);

      // Probe for browser-level cache hit (e.g. strip thumbnail already fetched).
      const probe = new Image();
      probe.src = src;
      if (probe.complete && probe.naturalWidth > 0) {
        LB_LOADED.add(src);
        clearTimeout(slowTimer.current);
        setLoaded(true);
        setSlowLoad(false);
      }
    }

    // preload prev + next (and keep refs so browser can't evict)
    const n = snap.items.length;
    [(i + 1) % n, (i - 1 + n) % n].forEach((k) => lbPreload(snap.items[k].src));

    return () => clearTimeout(slowTimer.current);
  }, [snap, i]);

  // Keyboard controls
  useEffectL(() => {
    if (!snap || !visible) return;
    const n = snap.items.length;
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      if (e.key === "ArrowRight") setI((x) => (x + 1) % n);
      if (e.key === "ArrowLeft") setI((x) => (x - 1 + n) % n);
    };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
    };
  }, [snap, visible, onClose]);

  // Sync <html> dataset so the CSS variants for layout/orientation/order match
  // the effective per-item layout (derived from item.type). We write to
  // `data-lightbox-layout` directly so existing CSS selectors keep working.
  // Must run unconditionally (before any early return) to respect Rules of Hooks.
  useEffectL(() => {
    if (!snap) return;
    const current = snap.items[i];
    const t = current.type || "image";
    const r = document.documentElement;
    if (t === "card-horizontal") {
      r.dataset.lightboxLayout = "card";
      r.dataset.cardOrientation = "horizontal";
      r.dataset.cardOrder = current.reverse ? "reverse" : "normal";
    } else if (t === "card-vertical") {
      r.dataset.lightboxLayout = "card";
      r.dataset.cardOrientation = "vertical";
      r.dataset.cardOrder = current.reverse ? "reverse" : "normal";
    } else if (t === "collage") {
      r.dataset.lightboxLayout = "collage";
    } else {
      r.dataset.lightboxLayout = "minimal";
    }
  }, [snap, i]);

  if (!snap) return null;
  const it = snap.items[i];
  const pad = (v) => String(v).padStart(2, "0");
  const n = snap.items.length;

  // Map per-item `type` to an effective lightbox layout.
  // Per spec:
  //   image | youtube | (missing)  → minimal
  //   collage                       → collage (editorial spread)
  //   card-horizontal               → card (horizontal orientation)
  //   card-vertical                 → card (vertical orientation)
  // `it.reverse` flips order for card types.
  const itemType = it.type || "image";
  let effectiveLayout = "minimal";
  let effectiveOrientation = null;
  let effectiveOrder = null;
  if (itemType === "card-horizontal") {
    effectiveLayout = "card";
    effectiveOrientation = "horizontal";
    effectiveOrder = it.reverse ? "reverse" : "normal";
  } else if (itemType === "card-vertical") {
    effectiveLayout = "card";
    effectiveOrientation = "vertical";
    effectiveOrder = it.reverse ? "reverse" : "normal";
  } else if (itemType === "collage") {
    effectiveLayout = "collage";
  }

  // Per spec: nav cursor + nav-on-click should work EVERYWHERE on the lightbox
  // EXCEPT when hovering the close (Esc) button. Other elements with their
  // own onClick (mosaic/contact/spread cells, prev/next arrows) keep their
  // stopPropagation so click selects them instead of navigating, but the nav
  // cursor still shows on hover for visual consistency.
  const isInteractive = (target) =>
    !!target.closest(".lightbox__close");

  const onBackdropMove = (e) => {
    if (isInteractive(e.target)) {
      if (cursorSide !== null) setCursorSide(null);
      return;
    }
    setCursorPos({ x: e.clientX, y: e.clientY });
    const half = window.innerWidth / 2;
    setCursorSide(e.clientX < half ? "prev" : "next");
  };

  const onBackdropClick = (e) => {
    if (isInteractive(e.target)) return;
    const half = window.innerWidth / 2;
    if (e.clientX < half) setI((x) => (x - 1 + n) % n);
    else setI((x) => (x + 1) % n);
  };

  const onImgLoad = () => {
    LB_LOADED.add(it.src);
    clearTimeout(slowTimer.current);
    setLoaded(true);
    setSlowLoad(false);
  };

  // Pre-allocate space for the image using its known aspect ratio so the
  // stage doesn't reflow when the image finishes loading. The wrapper sizes
  // itself to the image's natural ratio, capped to fit the stage box.
  const aspect = it.w && it.h ? it.w / it.h : null;
  const frameStyle = aspect ? { aspectRatio: String(aspect) } : undefined;

  const stageEl = (
    <div
      className={
        "lightbox__stage" +
        (loaded ? " is-loaded" : " is-loading") +
        (slowLoad && !loaded ? " is-slow" : "")
      }
    >
      <button
        className="lightbox__nav lightbox__nav--prev"
        onClick={(e) => { e.stopPropagation(); setI((x) => (x - 1 + n) % n); }}
        aria-label="anterior"
      >←</button>

      <div className="lightbox__frame" style={frameStyle}>
        {/* Spinner only appears after LB_SLOW_MS of unresolved loading */}
        <div className="lightbox__spinner" aria-hidden="true">
          <div className="lightbox__spinner-ring"></div>
          <div className="lightbox__spinner-label mono">CARGANDO</div>
        </div>

        <Img
          key={it.src /* force remount so onLoad fires for every new src */}
          src={it.src}
          alt={it.caption?.title || ""}
          aspectRatio={aspect || "auto"}
          fit="contain"
          eager
          spinner={false}
          onLoad={onImgLoad}
          onError={onImgLoad}
          fallbackLabel={(it.caption?.title || "IMAGEN").toUpperCase()}
        />
      </div>

      <button
        className="lightbox__nav lightbox__nav--next"
        onClick={(e) => { e.stopPropagation(); setI((x) => (x + 1) % n); }}
        aria-label="siguiente"
      >→</button>
    </div>
  );

  const cap = it.caption || {};
  const metaBits = [cap.location, cap.date].filter(Boolean);
  const captionEl = (
    <div className="lightbox__caption">
      <div className="lightbox__caption-index mono">{pad(i + 1)} / {pad(n)}</div>
      <div className="lightbox__caption-title serif">{cap.title}</div>
      <div className="lightbox__caption-meta mono">
        {metaBits.map((bit, idx) => (
          <React.Fragment key={idx}>
            <span>{bit}</span>
            <span className="lightbox__caption-sep">·</span>
          </React.Fragment>
        ))}
        <span>MLZ_{pad(i + 1)}_{snap.label.replace(/\s/g, "")}</span>
      </div>
      {cap.description && (
        <p className="lightbox__caption-desc serif">{cap.description}</p>
      )}
    </div>
  );

  return (
    <div
      className={"lightbox" + (visible ? " is-visible" : "")}
      onMouseMove={onBackdropMove}
      onMouseLeave={() => setCursorSide(null)}
      onClick={onBackdropClick}
      data-cursor={cursorSide || "none"}
    >
      <span className="lightbox__counter">{snap.label} · {pad(i + 1)} / {pad(n)}</span>

      <button
        className="lightbox__close"
        onClick={(e) => { e.stopPropagation(); onClose(); }}
        aria-label="cerrar"
      >
        <span className="lightbox__close-label">CERRAR</span>
        <span className="lightbox__close-glyph" aria-hidden="true">✕</span>
        <span className="lightbox__close-esc">ESC</span>
      </button>

      {effectiveLayout === "card" ? (
        <div className="lightbox__card">
          {stageEl}
          {captionEl}
        </div>
      ) : effectiveLayout === "collage" ? (
        <CollageSpreadDispatcher item={it} index={i} total={n} label={snap.label} />
      ) : stageEl}
      {/* SPREAD layout — editorial magazine spread: 5 neighbours in a grid next to the hero image */}
      {effectiveLayout === "spread" && (
        <div className="lightbox__spread-grid">
          {[1, 2, 3, 4, 5].map((off) => {
            const k = (i + off) % n;
            const sec = snap.items[k];
            return (
              <button
                key={off}
                className={"lightbox__spread-cell cell-" + off}
                onClick={(e) => { e.stopPropagation(); setI(k); }}
              >
                <Img src={sec.src} alt={sec.caption?.title || ""} fallbackLabel={(sec.caption?.title || "").toUpperCase()} />
                <span className="lightbox__spread-cap mono">{sec.caption?.location}</span>
              </button>
            );
          })}
        </div>
      )}

      {/* MOSAIC layout — all gallery items tiled below the stage, active one highlighted */}
      {effectiveLayout === "mosaic" && (
        <div className="lightbox__mosaic">
          {snap.items.map((it2, k) => (
            <button
              key={k}
              className={"lightbox__mosaic-cell" + (k === i ? " is-active" : "")}
              onClick={(e) => { e.stopPropagation(); setI(k); }}
            >
              <Img src={it2.src} alt={it2.caption?.title || ""} fallbackLabel={(it2.caption?.title || "").toUpperCase()} />
              <span className="lightbox__mosaic-num mono">{pad(k + 1)}</span>
            </button>
          ))}
        </div>
      )}

      {/* CONTACT layout — contact-sheet grid of all thumbs, no main stage */}
      {effectiveLayout === "contact" && (
        <div className="lightbox__contact">
          {snap.items.map((it2, k) => (
            <button
              key={k}
              className={"lightbox__contact-cell" + (k === i ? " is-active" : "")}
              onClick={(e) => { e.stopPropagation(); setI(k); }}
            >
              <Img src={it2.src} alt={it2.caption?.title || ""} fallbackLabel={(it2.caption?.title || "").toUpperCase()} />
              <span className="lightbox__contact-meta mono">
                <span>{pad(k + 1)}</span>
                <span>{it2.caption?.location}</span>
              </span>
            </button>
          ))}
        </div>
      )}

      {effectiveLayout !== "card" && effectiveLayout !== "collage" && (
        <div className="lightbox__caption">
          <div className="lightbox__caption-index mono">{pad(i + 1)} / {pad(n)}</div>
          <div className="lightbox__caption-title serif">{cap.title}</div>
          <div className="lightbox__caption-meta mono">
            {metaBits.map((bit, idx) => (
              <React.Fragment key={idx}>
                <span>{bit}</span>
                <span className="lightbox__caption-sep">·</span>
              </React.Fragment>
            ))}
            <span>MLZ_{pad(i + 1)}_{snap.label.replace(/\s/g, "")}</span>
          </div>
          {cap.description && (
            <p className="lightbox__caption-desc serif">{cap.description}</p>
          )}
        </div>
      )}

      {cursorSide && (
        <div
          className="lightbox__cursor"
          style={{ transform: `translate(${cursorPos.x}px, ${cursorPos.y}px)` }}
          data-side={cursorSide}
        >
          <span className="lightbox__cursor-arrow">{cursorSide === "prev" ? "←" : "→"}</span>
          <span className="lightbox__cursor-label mono">{cursorSide === "prev" ? "ANT" : "SIG"}</span>
        </div>
      )}
    </div>
  );
}

window.Lightbox = Lightbox;
