/* global React */
const { useEffect, useRef, useState } = React;

function useNearViewport(rootMargin = "900px") {
  const ref = useRef(null);
  const [near, setNear] = useState(false);

  useEffect(() => {
    const el = ref.current;
    if (!el || near) return;
    if (!("IntersectionObserver" in window)) {
      setNear(true);
      return;
    }
    const observer = new IntersectionObserver((entries) => {
      if (entries.some((entry) => entry.isIntersecting)) {
        setNear(true);
        observer.disconnect();
      }
    }, { rootMargin });
    observer.observe(el);
    return () => observer.disconnect();
  }, [near, rootMargin]);

  return [ref, near];
}

function LazyImage({ src, alt = "", className = "", rootMargin = "900px", ...props }) {
  const [ref, near] = useNearViewport(rootMargin);
  return (
    <img
      ref={ref}
      src={near ? src : undefined}
      alt={alt}
      loading="lazy"
      decoding="async"
      className={className}
      {...props}
    />
  );
}

function LazyHoverVideo({ src, className = "", rootMargin = "900px", ...props }) {
  const [ref, near] = useNearViewport(rootMargin);
  return (
    <video
      ref={ref}
      src={near ? src : undefined}
      muted
      loop
      playsInline
      preload="none"
      className={className}
      onMouseEnter={(e) => {
        if (!near) return;
        e.currentTarget.play().catch(() => {});
      }}
      onMouseLeave={(e) => {
        e.currentTarget.pause();
        e.currentTarget.currentTime = 0;
      }}
      {...props}
    />
  );
}

/* ─────────── FadingVideo ─────────── */
/* rAF-driven crossfade. No CSS transitions. */
function FadingVideo({ src, className = "", style = {}, poster, fadeMs = 500, fadeLead = 0.55, loop = false, audible = false, videoId }) {
  const ref = useRef(null);
  const rafRef = useRef(null);
  const fadingOutRef = useRef(false);

  useEffect(() => {
    const video = ref.current;
    if (!video) return;
    // In loop mode we keep the element visible so its poster paints instantly
    // on mount and the first decoded frame replaces it seamlessly (no black gap).
    if (!loop) video.style.opacity = "0";

    const cancel = () => {
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      rafRef.current = null;
    };
    const fadeTo = (target, dur = fadeMs) => {
      cancel();
      const start = performance.now();
      const from = parseFloat(video.style.opacity || "0") || 0;
      const step = (t) => {
        const k = Math.min(1, (t - start) / dur);
        const eased = k * (2 - k);
        video.style.opacity = String(from + (target - from) * eased);
        if (k < 1) rafRef.current = requestAnimationFrame(step);
        else cancel();
      };
      rafRef.current = requestAnimationFrame(step);
    };

    // Resilient autoplay: muted autoplay can still be rejected on the first
    // attempt (timing / policy). Retry on later readiness events and on the
    // first user interaction so the hero never gets stuck on a frozen frame.
    let played = false;
    const tryPlay = () => {
      if (!audible) { video.muted = true; video.volume = 0; }
      const p = video.play();
      if (p && p.then) {
        p.then(() => { played = true; }).catch(() => {
          if (audible) {
            // Unmuted autoplay blocked by policy — play muted so motion shows;
            // MusicToggle unmutes on the first user interaction.
            video.muted = true;
            const p2 = video.play();
            if (p2 && p2.catch) p2.catch(() => {});
          }
        });
      } else {
        played = true;
      }
    };
    const onInteract = () => { if (!played) tryPlay(); };

    const onLoaded = () => {
      tryPlay();
      if (loop) {
        // already visible (poster → first frame); make sure it's at full opacity
        video.style.opacity = "1";
        // hide the pre-React static poster now that the live video is up
        const sp = document.getElementById("hero-static-poster");
        if (sp) sp.style.opacity = "0";
      } else {
        video.style.opacity = "0";
        fadeTo(1);
      }
    };
    // Custom fade-out + reset only when not using native loop
    const onTime = () => {
      if (loop) return;
      if (fadingOutRef.current) return;
      const left = (video.duration || 0) - video.currentTime;
      if (left > 0 && left <= fadeLead) {
        fadingOutRef.current = true;
        fadeTo(0);
      }
    };
    const onEnded = () => {
      if (loop) return;
      video.style.opacity = "0";
      setTimeout(() => {
        try {
          video.currentTime = 0;
          const p = video.play();
          if (p && p.catch) p.catch(() => {});
        } catch (e) {}
        fadingOutRef.current = false;
        fadeTo(1);
      }, 100);
    };

    video.addEventListener("loadeddata", onLoaded);
    video.addEventListener("canplay", tryPlay);
    video.addEventListener("canplaythrough", tryPlay);
    video.addEventListener("timeupdate", onTime);
    video.addEventListener("ended", onEnded);
    window.addEventListener("pointerdown", onInteract, { passive: true });
    window.addEventListener("keydown", onInteract);
    if (video.readyState >= 2) onLoaded();

    return () => {
      cancel();
      video.removeEventListener("loadeddata", onLoaded);
      video.removeEventListener("canplay", tryPlay);
      video.removeEventListener("canplaythrough", tryPlay);
      video.removeEventListener("timeupdate", onTime);
      video.removeEventListener("ended", onEnded);
      window.removeEventListener("pointerdown", onInteract);
      window.removeEventListener("keydown", onInteract);
    };
  }, [src, fadeMs, fadeLead, loop, audible]);

  return (
    <video
      ref={ref}
      id={videoId}
      src={src}
      poster={poster}
      autoPlay
      muted={!audible}
      playsInline
      preload="auto"
      loop={loop}
      className={className}
      style={{ opacity: loop ? 1 : 0, ...style }}
    />
  );
}

/* ─────────── BlurText (word-by-word entrance) ─────────── */
function BlurText({ text, className = "", style = {}, delay = 0, stagger = 0.06 }) {
  const { motion } = window.Motion || {};
  if (!motion) return <span className={className} style={style}>{text}</span>;
  const words = String(text).split(/(\s+)/); // keep spaces
  let wIdx = -1;
  return (
    <span className={className} style={{ display: "inline-flex", flexWrap: "wrap", justifyContent: "center", rowGap: "0.05em", ...style }}>
      {words.map((w, i) => {
        if (/^\s+$/.test(w)) return <span key={`sp-${i}`}>&nbsp;</span>;
        wIdx += 1;
        const d = delay + wIdx * stagger;
        return (
          <motion.span
            key={`w-${i}`}
            style={{ display: "inline-block", marginRight: "0.18em" }}
            initial={{ filter: "blur(10px)", opacity: 0, y: 30 }}
            animate={{ filter: ["blur(10px)", "blur(4px)", "blur(0px)"], opacity: [0, 0.6, 1], y: [30, -4, 0] }}
            transition={{ duration: 0.9, times: [0, 0.5, 1], delay: d, ease: [0.22, 0.61, 0.36, 1] }}
          >{w}</motion.span>
        );
      })}
    </span>
  );
}

/* ─────────── Reveal wrapper (scroll-based, no IO) ─────────── */
const _revealRegistry = new Set();
function _revealTick() {
  const wh = window.innerHeight;
  _revealRegistry.forEach((entry) => {
    if (entry.shown) return;
    const el = entry.ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    // Trigger if element top has entered the viewport (or scrolled past)
    if (r.top < wh * 0.92) {
      entry.shown = true;
      entry.set(true);
    }
  });
}
if (typeof window !== "undefined" && !window.__revealAttached) {
  window.__revealAttached = true;
  window.addEventListener("scroll", _revealTick, { passive: true });
  window.addEventListener("resize", _revealTick);
  // run a tick after first paint to catch initial state
  requestAnimationFrame(() => requestAnimationFrame(_revealTick));
}

function Reveal({ children, delay = 0, y = 16, className }) {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const entry = { ref, shown: false, set: setShown };
    _revealRegistry.add(entry);
    _revealTick();
    return () => { _revealRegistry.delete(entry); };
  }, []);
  const style = {
    transition: `opacity 700ms cubic-bezier(0.22,0.61,0.36,1) ${delay}s, transform 700ms cubic-bezier(0.22,0.61,0.36,1) ${delay}s, filter 700ms cubic-bezier(0.22,0.61,0.36,1) ${delay}s`,
    opacity: shown ? 1 : 0,
    transform: shown ? "translateY(0)" : `translateY(${y}px)`,
    filter: shown ? "blur(0px)" : "blur(8px)",
    willChange: shown ? "auto" : "opacity, transform, filter"
  };
  return (
    <div ref={ref} className={className} style={style}>
      {children}
    </div>
  );
}

/* ─────────── Galaxy starfield (canvas overlay) ─────────── */
function Starfield({ density = 120, className = "", style = {} }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let w = 0, h = 0, dpr = Math.min(1.5, window.devicePixelRatio || 1);
    let visible = true;
    const stars = [];
    const resize = () => {
      const r = c.getBoundingClientRect();
      w = r.width; h = r.height;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      stars.length = 0;
      for (let i = 0; i < density; i++) {
        stars.push({
          x: Math.random() * w,
          y: Math.random() * h,
          r: Math.random() * 1.2 + 0.2,
          a: Math.random() * 0.6 + 0.2,
          tw: Math.random() * 0.03 + 0.005,
          ph: Math.random() * Math.PI * 2
        });
      }
    };
    let raf, t0 = performance.now();
    const tick = (t) => {
      if (!visible) {
        raf = requestAnimationFrame(tick);
        return;
      }
      const dt = (t - t0) * 0.001; t0 = t;
      ctx.clearRect(0, 0, w, h);
      for (const s of stars) {
        s.ph += s.tw * 16;
        const alpha = s.a * (0.6 + 0.4 * Math.sin(s.ph));
        ctx.beginPath();
        ctx.fillStyle = `rgba(255,255,255,${alpha})`;
        ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
        ctx.fill();
      }
      raf = requestAnimationFrame(tick);
    };
    resize();
    raf = requestAnimationFrame(tick);
    let observer;
    if ("IntersectionObserver" in window) {
      observer = new IntersectionObserver((entries) => {
        visible = entries.some((entry) => entry.isIntersecting);
      });
      observer.observe(c);
    }
    window.addEventListener("resize", resize);
    return () => {
      cancelAnimationFrame(raf);
      if (observer) observer.disconnect();
      window.removeEventListener("resize", resize);
    };
  }, [density]);
  return <canvas ref={ref} className={className} style={{ width: "100%", height: "100%", display: "block", ...style }} />;
}

/* ─────────── Small primitives ─────────── */
function GlassChip({ children, className = "", strong = false }) {
  return (
    <span className={(strong ? "liquid-glass-strong" : "liquid-glass") + " inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs text-white/85 " + className}>
      {children}
    </span>
  );
}

function GlassButton({ children, className = "", strong = false, as = "button", href, onClick }) {
  const cls = (strong ? "liquid-glass-strong" : "liquid-glass") + " inline-flex items-center gap-2 rounded-full px-5 py-2.5 text-sm text-white hover:text-white/95 transition-colors " + className;
  if (as === "a") return <a href={href} className={cls} onClick={onClick}>{children}</a>;
  return <button className={cls} onClick={onClick}>{children}</button>;
}

/* ─────────── Icons (inline lucide-style) ─────────── */
function Icon({ name, className = "h-4 w-4", strokeWidth = 1.5 }) {
  const p = { fill: "none", stroke: "currentColor", strokeWidth, strokeLinecap: "round", strokeLinejoin: "round" };
  switch (name) {
    case "arrow-up-right":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M7 17 17 7" /><path d="M7 7h10v10" /></svg>;
    case "arrow-right":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M5 12h14" /><path d="M13 5l7 7-7 7" /></svg>;
    case "arrow-down":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M12 5v14" /><path d="M5 13l7 7 7-7" /></svg>;
    case "play":
      return <svg className={className} viewBox="0 0 24 24" fill="currentColor"><polygon points="6 4 20 12 6 20" /></svg>;
    case "sparkle":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M12 3v4" /><path d="M12 17v4" /><path d="M3 12h4" /><path d="M17 12h4" /><path d="M6 6l3 3" /><path d="M15 15l3 3" /><path d="M18 6l-3 3" /><path d="M9 15l-3 3" /></svg>;
    case "globe":
      return <svg className={className} viewBox="0 0 24 24" {...p}><circle cx="12" cy="12" r="9" /><path d="M3 12h18" /><path d="M12 3a14 14 0 0 1 0 18a14 14 0 0 1 0-18" /></svg>;
    case "github":
      return <svg className={className} viewBox="0 0 24 24" fill="currentColor"><path d="M12 2a10 10 0 0 0-3.16 19.49c.5.09.68-.22.68-.48v-1.7c-2.78.6-3.37-1.34-3.37-1.34-.45-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.89 1.52 2.34 1.08 2.91.83.09-.65.35-1.08.63-1.33-2.22-.25-4.55-1.11-4.55-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.65 0 0 .84-.27 2.75 1.02a9.6 9.6 0 0 1 5 0c1.91-1.29 2.75-1.02 2.75-1.02.55 1.38.2 2.4.1 2.65.64.7 1.03 1.59 1.03 2.68 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85v2.75c0 .27.18.58.69.48A10 10 0 0 0 12 2Z"/></svg>;
    case "external":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M14 4h6v6" /><path d="M20 4 10 14" /><path d="M20 14v6H4V4h6" /></svg>;
    case "camera":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M3 7h4l2-3h6l2 3h4v13H3z" /><circle cx="12" cy="13" r="4" /></svg>;
    case "plane":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M3 13l18-7-7 18-2-8-9-3z" /></svg>;
    case "home":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M3 11l9-8 9 8" /><path d="M5 10v10h14V10" /></svg>;
    case "cards":
      return <svg className={className} viewBox="0 0 24 24" {...p}><rect x="3" y="5" width="14" height="14" rx="2" /><rect x="7" y="9" width="14" height="10" rx="2" /></svg>;
    case "cat":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M12 5 9.5 3v3.5" /><path d="M12 5l2.5-2v3.5" /><path d="M5 8.5C5 7 7 6.5 9.5 6.5h5C17 6.5 19 7 19 8.5c.6.5 1 1.4 1 2.5v5a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4v-5c0-1.1.4-2 1-2.5Z" /><path d="M9.5 13h.01" /><path d="M14.5 13h.01" /><path d="M10.5 16.5s.7.7 1.5.7 1.5-.7 1.5-.7" /></svg>;
    case "wand":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M3 21 14 10" /><path d="M14 3l2 5 5 2-5 2-2 5-2-5-5-2 5-2z" /></svg>;
    case "list":
      return <svg className={className} viewBox="0 0 24 24" {...p}><path d="M8 6h13" /><path d="M8 12h13" /><path d="M8 18h13" /><circle cx="4" cy="6" r="1.2" /><circle cx="4" cy="12" r="1.2" /><circle cx="4" cy="18" r="1.2" /></svg>;
    default:
      return null;
  }
}

/* ─────────── Export to window ─────────── */
Object.assign(window, { LazyImage, LazyHoverVideo, FadingVideo, BlurText, Reveal, Starfield, GlassChip, GlassButton, Icon });
