// ============================================================
// <Img> — unified image component for the whole site.
//
// Behaviour incorporated:
//   - Lazy-loading by default (browser-level loading="lazy"),
//     overridable with `eager`.
//   - Pre-allocates space using either explicit `width/height`,
//     a numeric `aspectRatio` (w/h), or an `aspectRatio` CSS
//     string ("4/3"). Prevents layout reflow when the image
//     finishes loading.
//   - Session cache (`IMG_LOADED`) so re-mounting an already
//     loaded URL skips the fade-in / spinner entirely.
//   - Slow-load spinner: only shows the spinner after
//     `slowMs` of unresolved loading (default 175ms) — quick
//     loads stay invisible, real waits get feedback.
//   - Fallback: toned-stripe SVG placeholder with mono label
//     when the src errors out. Matches the project's
//     `placeholder()` helper exactly. Customise per-instance
//     with `fallbackLabel` / `fallbackTone`.
//   - Manual preload API (`Img.preload(src)`) for adjacent
//     images (used by the lightbox).
//   - `onLoad` / `onError` hooks still fire for callers.
//   - All props pass through to the <img> (e.g. `draggable`,
//     `style`, additional className tokens via `imgClassName`).
//
// The component renders a wrapper <div class="img"> + the
// <img> inside. Wrapper sizes the layout, image fills it.
// Style hooks are scoped under .img so existing layout CSS
// (which targets `.tile img`, `.cstack__photo img`, etc.)
// keeps working unchanged.
// ============================================================

const {
  useState: useStateImg,
  useEffect: useEffectImg,
  useRef: useRefImg,
} = React;

// Module-level cache of URLs that have already loaded successfully
// (or errored) in this session. Survives unmount/remount.
const IMG_LOADED = new Set();
const IMG_ERRORED = new Set();
// Strong refs to in-flight preloads so the browser can't evict.
const IMG_PRELOAD_REFS = new Map();

const SLOW_MS_DEFAULT = 175;

// Toned-stripe SVG placeholder. Mirrors `placeholder()` in data.js
// so error fallbacks are visually consistent with intentional ones.
function fallbackDataUrl(
  label = "IMAGEN NO DISPONIBLE",
  tone = "#1a1a1a",
  w = 600,
  h = 600,
) {
  const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${w}' height='${h}' viewBox='0 0 ${w} ${h}'>
    <defs>
      <pattern id='p' patternUnits='userSpaceOnUse' width='14' height='14' patternTransform='rotate(45)'>
        <rect width='14' height='14' fill='${tone}'/>
        <line x1='0' y1='0' x2='0' y2='14' stroke='rgba(255,255,255,0.04)' stroke-width='7'/>
      </pattern>
    </defs>
    <rect width='100%' height='100%' fill='url(#p)'/>
    <text x='50%' y='50%' text-anchor='middle' dominant-baseline='middle'
      font-family='JetBrains Mono, monospace' font-size='11' fill='rgba(255,255,255,0.55)'
      letter-spacing='2'>${label}</text>
  </svg>`;
  return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
}

// Manual preload — used by the lightbox for adjacent images.
function imgPreload(src) {
  if (!src) return;
  if (IMG_LOADED.has(src) || IMG_PRELOAD_REFS.has(src)) return;
  const probe = new Image();
  probe.onload = () => {
    IMG_LOADED.add(src);
    IMG_PRELOAD_REFS.delete(src);
  };
  probe.onerror = () => {
    IMG_ERRORED.add(src);
    IMG_PRELOAD_REFS.delete(src);
  };
  probe.src = src;
  IMG_PRELOAD_REFS.set(src, probe);
}

// Synchronous probe — if the image is already in the browser cache
// (e.g. the strip thumbnail when opening the lightbox), mark it loaded
// so we never flash a loading state for it.
function probeCached(src) {
  if (!src || IMG_LOADED.has(src)) return false;
  const probe = new Image();
  probe.src = src;
  if (probe.complete && probe.naturalWidth > 0) {
    IMG_LOADED.add(src);
    return true;
  }
  return false;
}

function Img({
  src,
  alt = "",
  // Pre-allocation. Pick whichever is convenient:
  //   aspectRatio = number (w/h) | string ("4/3") | "auto"
  //   width + height (numbers, treated as ratio if no aspectRatio)
  aspectRatio,
  width,
  height,
  // Object-fit (defaults cover — most uses). Use "contain" for the
  // lightbox stage where the full image must be visible.
  fit = "cover",
  // Loading.
  eager = false,
  slowMs = SLOW_MS_DEFAULT,
  // Suppress the built-in slow spinner (caller renders its own).
  spinner = true,
  // Fallback when src errors. `null` to disable (just hide).
  fallbackLabel,
  fallbackTone,
  // Class hooks.
  className = "",
  imgClassName = "",
  // Style on wrapper.
  style,
  // Pass-through.
  draggable,
  onLoad,
  onError,
  // Anything else lands on the <img>.
  ...rest
}) {
  const [loaded, setLoaded] = useStateImg(() => !!src && IMG_LOADED.has(src));
  const [errored, setErrored] = useStateImg(
    () => !!src && IMG_ERRORED.has(src),
  );
  const [slow, setSlow] = useStateImg(false);
  const slowTimer = useRefImg(null);

  // When src changes: reset state, fast-path through cache, schedule
  // slow-load spinner, probe browser cache.
  useEffectImg(() => {
    clearTimeout(slowTimer.current);
    if (!src) {
      setLoaded(false);
      setErrored(false);
      setSlow(false);
      return;
    }
    if (IMG_LOADED.has(src)) {
      setLoaded(true);
      setErrored(false);
      setSlow(false);
      return;
    }
    if (IMG_ERRORED.has(src)) {
      setLoaded(false);
      setErrored(true);
      setSlow(false);
      return;
    }
    // Synchronously probe the browser HTTP cache.
    if (probeCached(src)) {
      setLoaded(true);
      setErrored(false);
      setSlow(false);
      return;
    }
    setLoaded(false);
    setErrored(false);
    setSlow(false);
    slowTimer.current = setTimeout(() => setSlow(true), slowMs);
    return () => clearTimeout(slowTimer.current);
  }, [src, slowMs]);

  const handleLoad = (e) => {
    if (src) IMG_LOADED.add(src);
    clearTimeout(slowTimer.current);
    setLoaded(true);
    setSlow(false);
    if (onLoad) onLoad(e);
  };

  const handleError = (e) => {
    if (src) IMG_ERRORED.add(src);
    clearTimeout(slowTimer.current);
    setErrored(true);
    setSlow(false);
    if (onError) onError(e);
  };

  // Resolve aspect-ratio CSS for the wrapper.
  let arCss;
  if (aspectRatio === "auto" || aspectRatio === false) {
    arCss = undefined;
  } else if (
    typeof aspectRatio === "number" &&
    isFinite(aspectRatio) &&
    aspectRatio > 0
  ) {
    arCss = String(aspectRatio);
  } else if (typeof aspectRatio === "string" && aspectRatio) {
    arCss = aspectRatio;
  } else if (
    typeof width === "number" &&
    typeof height === "number" &&
    height > 0
  ) {
    arCss = `${width} / ${height}`;
  }

  // The actual <img> src — swap to fallback if errored.
  const finalSrc = errored
    ? fallbackLabel === null
      ? undefined
      : fallbackDataUrl(
          fallbackLabel || "IMAGEN NO DISPONIBLE",
          fallbackTone || "#1a1a1a",
        )
    : src;

  const wrapperStyle = { ...(style || {}) };
  if (arCss) wrapperStyle.aspectRatio = arCss;

  const wrapperClass = [
    "img",
    `img--fit-${fit}`,
    loaded ? "img--loaded" : "img--loading",
    errored ? "img--errored" : "",
    slow && !loaded && !errored ? "img--slow" : "",
    className,
  ]
    .filter(Boolean)
    .join(" ");

  return (
    <span className={wrapperClass} style={wrapperStyle}>
      {/* Slow-load spinner — only painted when .img--slow is set */}
      {spinner && (
        <span className="img__spinner" aria-hidden="true">
          <span className="img__spinner-ring" />
        </span>
      )}
      {finalSrc && (
        <img
          {...rest}
          src={finalSrc}
          alt={alt}
          loading={eager ? "eager" : "lazy"}
          decoding="async"
          draggable={draggable === undefined ? false : draggable}
          onLoad={handleLoad}
          onError={handleError}
          className={imgClassName}
        />
      )}
    </span>
  );
}

// Static methods on the component.
Img.preload = imgPreload;
Img.markLoaded = (src) => {
  if (src) IMG_LOADED.add(src);
};
Img.isLoaded = (src) => !!src && IMG_LOADED.has(src);
Img.fallbackDataUrl = fallbackDataUrl;

window.Img = Img;
window.imgPreload = imgPreload;
