// AMS — interaction primitives.
// Reveal, Counter, Marquee, Carousel, useScrollSpy, useStickyShrink.

const { useState, useEffect, useRef, useCallback } = React;

/* ───────── Reveal: scroll-triggered fade/slide-up ───────── */
function Reveal({ children, as = "div", delay = 0, distance = 24, className = "", ...rest }) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    if (typeof IntersectionObserver === "undefined") { setShown(true); return; }
    const obs = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) { setShown(true); obs.disconnect(); }
      });
    }, { threshold: 0.12, rootMargin: "0px 0px -40px 0px" });
    obs.observe(ref.current);
    return () => obs.disconnect();
  }, []);
  const Tag = as;
  const style = {
    opacity: shown ? 1 : 0,
    transform: shown ? "none" : `translateY(${distance}px)`,
    transition: `opacity 720ms cubic-bezier(0.22,1,0.36,1) ${delay}ms, transform 720ms cubic-bezier(0.22,1,0.36,1) ${delay}ms`,
    willChange: "opacity, transform",
  };
  return <Tag ref={ref} className={className} style={style} {...rest}>{children}</Tag>;
}

/* ───────── Counter: animated number when in view ───────── */
function Counter({ to, duration = 1400, suffix = "", prefix = "", decimals = 0 }) {
  const ref = useRef(null);
  const [val, setVal] = useState(0);
  const [started, setStarted] = useState(false);
  useEffect(() => {
    if (!ref.current || started) return;
    const obs = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setStarted(true); obs.disconnect(); } });
    }, { threshold: 0.4 });
    obs.observe(ref.current);
    return () => obs.disconnect();
  }, [started]);
  useEffect(() => {
    if (!started) return;
    const start = performance.now();
    let raf;
    const tick = (t) => {
      const p = Math.min(1, (t - start) / duration);
      const eased = 1 - Math.pow(1 - p, 3);
      setVal(to * eased);
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [started, to, duration]);
  const display = decimals ? val.toFixed(decimals) : Math.round(val).toLocaleString();
  return <span ref={ref}>{prefix}{display}{suffix}</span>;
}

/* ───────── Marquee: infinite horizontal scroll ───────── */
function Marquee({ items, speed = 50, className = "" }) {
  // duplicate items so the loop is seamless
  const dup = [...items, ...items];
  const dur = (items.length * 220) / speed; // longer list = longer cycle
  return (
    <div className={"ams-marquee " + className} aria-hidden="true">
      <div className="ams-marquee-track" style={{ animationDuration: dur + "s" }}>
        {dup.map((it, i) => (
          <span key={i} className="ams-marquee-item">
            {it}
            <span className="ams-marquee-dot" aria-hidden="true">●</span>
          </span>
        ))}
      </div>
    </div>
  );
}

/* ───────── Carousel: auto-advance with manual dots ───────── */
function Carousel({ items, renderItem, interval = 6000, className = "" }) {
  const [i, setI] = useState(0);
  const [paused, setPaused] = useState(false);
  useEffect(() => {
    if (paused) return;
    const id = setInterval(() => setI((v) => (v + 1) % items.length), interval);
    return () => clearInterval(id);
  }, [items.length, interval, paused]);
  return (
    <div
      className={"ams-carousel " + className}
      onMouseEnter={() => setPaused(true)}
      onMouseLeave={() => setPaused(false)}
    >
      <div className="ams-carousel-viewport">
        <div
          className="ams-carousel-track"
          style={{ transform: `translateX(${-i * 100}%)` }}
        >
          {items.map((it, idx) => (
            <div key={idx} className="ams-carousel-slide">{renderItem(it, idx)}</div>
          ))}
        </div>
      </div>
      <div className="ams-carousel-dots" role="tablist" aria-label="Reviews">
        {items.map((_, idx) => (
          <button
            key={idx}
            role="tab"
            aria-selected={i === idx}
            aria-label={"Show review " + (idx + 1)}
            className={"ams-carousel-dot" + (i === idx ? " is-active" : "")}
            onClick={() => setI(idx)}
          />
        ))}
      </div>
    </div>
  );
}

/* ───────── useStickyShrink — track scroll past threshold ───────── */
function useStickyShrink(threshold = 24) {
  const [shrunk, setShrunk] = useState(false);
  useEffect(() => {
    const onScroll = () => setShrunk(window.scrollY > threshold);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, [threshold]);
  return shrunk;
}

/* ───────── Parallax — translateY on scroll ───────── */
function Parallax({ children, intensity = 0.18, className = "" }) {
  const ref = useRef(null);
  const [y, setY] = useState(0);
  useEffect(() => {
    let raf = null;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = null;
        if (!ref.current) return;
        const r = ref.current.getBoundingClientRect();
        const center = r.top + r.height / 2 - window.innerHeight / 2;
        setY(-center * intensity);
      });
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => { window.removeEventListener("scroll", onScroll); if (raf) cancelAnimationFrame(raf); };
  }, [intensity]);
  return (
    <div ref={ref} className={"ams-parallax-wrap " + className}>
      <div className="ams-parallax-inner" style={{ transform: `translate3d(0,${y}px,0)` }}>
        {children}
      </div>
    </div>
  );
}

window.AMSPrim = { Reveal, Counter, Marquee, Carousel, useStickyShrink, Parallax };
