"use client";

import { useEffect, useRef } from "react";

/**
 * Full-bleed image that drifts against the scroll.
 *
 * Follows the same rules as the hero canvas: one queued RAF at a time, and the
 * transform written straight to the node rather than through React state — a
 * state update per scroll tick would re-render the whole page section.
 *
 * The image is over-scaled so the drift never exposes an edge.
 */
export default function ParallaxImage({
  src,
  alt,
  fallbackSrc,
  strength = 0.16,
  className = "",
}: {
  src: string;
  alt: string;
  /** Swapped in if `src` 404s, so a missing asset degrades to a real image
   *  rather than a broken-image icon across the full width of a hero. */
  fallbackSrc?: string;
  /** Fraction of the element's height the image travels across the viewport. */
  strength?: number;
  className?: string;
}) {
  const wrapRef = useRef<HTMLDivElement>(null);
  const imgRef = useRef<HTMLImageElement>(null);
  const tickingRef = useRef(false);

  useEffect(() => {
    const img = imgRef.current;
    if (!img) return;
    // The <img> is server-rendered, so a 404 usually resolves before React
    // hydrates and attaches onError — that handler alone misses the common
    // case. Catch an already-failed image here, for both recovery paths.
    if (img.complete && img.naturalWidth === 0) {
      if (fallbackSrc) img.src = fallbackSrc;
      else img.style.visibility = "hidden";
    }
  }, [fallbackSrc]);

  useEffect(() => {
    const wrap = wrapRef.current;
    const img = imgRef.current;
    if (!wrap || !img) return;

    // Honour the OS setting: hold the image still rather than drifting it.
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;

    const scale = 1 + strength * 2;
    const update = () => {
      const rect = wrap.getBoundingClientRect();
      const vh = window.innerHeight;
      // -1 when the element sits below the fold, +1 once it has passed above.
      const t = (rect.top + rect.height / 2 - vh / 2) / (vh / 2 + rect.height / 2);
      const shift = -t * strength * rect.height;
      img.style.transform = `translate3d(0, ${shift.toFixed(2)}px, 0) scale(${scale})`;
      tickingRef.current = false;
    };

    const onScroll = () => {
      if (tickingRef.current) return;
      tickingRef.current = true;
      requestAnimationFrame(update);
    };

    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
  }, [strength]);

  return (
    <div ref={wrapRef} className={`absolute inset-0 overflow-hidden ${className}`}>
      <img
        ref={imgRef}
        src={src}
        alt={alt}
        onError={(e) => {
          const img = e.currentTarget;
          if (fallbackSrc && !img.src.endsWith(fallbackSrc)) {
            img.src = fallbackSrc;
          } else {
            // No fallback to fall back to: hide rather than leave a broken
            // image sitting across the full width of a section.
            img.style.visibility = "hidden";
          }
        }}
        className="h-full w-full object-cover will-change-transform"
      />
    </div>
  );
}
