/* global React */

/**
 * primitives.jsx
 *
 * Shared utility hooks and low-level components used across all sections.
 *
 * Exports (via window.*):
 *  useInView   — IntersectionObserver hook; fires once when element enters viewport
 *  Reveal      — wrapper that applies a CSS animation class when in view
 *  CountUp     — animates a number from 0 → end when scrolled into view
 *  ArrowRight  — inline SVG right-pointing arrow icon
 *  ArrowUpRight — inline SVG diagonal arrow icon (used for external links)
 */

const { useState: useStateP, useEffect: useEffectP, useRef: useRefP } = React;

/**
 * useInView — fires once when the referenced element enters the viewport.
 *
 * @param {object} options
 * @param {number} options.threshold  — visibility ratio to trigger (default 0.15)
 * @param {string} options.margin     — rootMargin for the observer (default "-60px")
 * @returns {[ref, boolean]} — [ref to attach, inView state]
 */
function useInView(options = {}) {
  const ref = useRefP(null);
  const [inView, setInView] = useStateP(false);
  useEffectP(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setInView(true);
          io.unobserve(el);
        }
      },
      { threshold: options.threshold ?? 0.15, rootMargin: options.margin || "-60px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

/**
 * Reveal — adds a CSS animation class to the wrapped element when it enters
 * the viewport. Works by toggling "in-view" on the element; the actual
 * keyframe is defined in styles.css (.fade-up.in-view, .slide-up.in-view).
 *
 * @param {React.ReactNode} children
 * @param {string} as          — HTML tag or component to render (default "div")
 * @param {string} variant     — CSS animation class name (default "fade-up")
 * @param {number} delay       — animationDelay in seconds (default 0)
 */
function Reveal({ children, as: As = "div", className = "", variant = "fade-up", delay = 0, style, ...rest }) {
  const [ref, inView] = useInView();
  return (
    <As
      ref={ref}
      className={`${variant} ${inView ? "in-view" : ""} ${className}`}
      style={{ animationDelay: `${delay}s`, ...style }}
      {...rest}
    >
      {children}
    </As>
  );
}

/**
 * CountUp — animates an integer from 0 → end using a cubic ease-out curve.
 * Animation starts the first time the element scrolls into view.
 *
 * @param {number} end       — target number to count to
 * @param {string} suffix    — appended after the number (e.g. "+", "%")
 * @param {number} duration  — animation duration in ms (default 1400)
 */
function CountUp({ end, suffix = "", duration = 1400 }) {
  const [ref, inView] = useInView();
  const [val, setVal] = useStateP(0);
  useEffectP(() => {
    if (!inView) return;
    const start = performance.now();
    let raf;
    const step = (t) => {
      const p = Math.min(1, (t - start) / duration);
      const eased = 1 - Math.pow(1 - p, 3);
      setVal(Math.round(eased * end));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [inView]);
  return (
    <span ref={ref}>
      {val}
      {suffix}
    </span>
  );
}

function ArrowRight({ size = 14 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="arrow">
      <path d="M5 12h14" />
      <path d="m12 5 7 7-7 7" />
    </svg>
  );
}
function ArrowUpRight({ size = 14 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="arrow">
      <path d="M7 17 17 7" />
      <path d="M7 7h10v10" />
    </svg>
  );
}

// Expose all primitives globally so downstream scripts can use them
Object.assign(window, { useInView, Reveal, CountUp, ArrowRight, ArrowUpRight });
