// primitives.jsx — shared building blocks (exported to window)
const { useRef, useEffect, useState, useCallback } = React;

/* IntersectionObserver: add .in once when element enters view */
function useInView(opts = {}) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    const el = ref.current;if (!el) return;
    if (typeof IntersectionObserver === "undefined") {setSeen(true);return;}
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) {setSeen(true);io.disconnect();}
    }, { threshold: opts.threshold ?? 0.18, rootMargin: opts.rootMargin ?? "0px 0px -8% 0px" });
    io.observe(el);return () => io.disconnect();
  }, []);
  return [ref, seen];
}

/* generic reveal-on-scroll */
function Reveal({ children, as = "div", delay = 0, className = "", style = {}, ...rest }) {
  const [ref, seen] = useInView();
  const As = as;
  return <As ref={ref} className={`reveal ${seen ? "in" : ""} ${className}`}
  style={{ "--d": delay + "ms", ...style }} {...rest}>{children}</As>;
}

/* headline that wipes up line-by-line. Pass an array of lines. */
function ClipLines({ lines, className = "", delay = 0, step = 90, style = {} }) {
  const [ref, seen] = useInView({ threshold: 0.3 });
  return (
    <span ref={ref} className={`${seen ? "in" : ""} ${className}`} style={style}>
      {lines.map((l, i) =>
      <span className="clip-line" key={i} style={{ "--d": delay + i * step + "ms" }}>
          <span>{l}</span>
        </span>
      )}
    </span>);

}

/* count-up number when in view */
function Counter({ to, prefix = "", suffix = "", decimals = 0, dur = 1600, className = "", style = {} }) {
  const [ref, seen] = useInView({ threshold: 0.5 });
  const [val, setVal] = useState(0);
  useEffect(() => {
    if (!seen) return;
    let raf, start;
    const ease = (t) => 1 - Math.pow(1 - t, 3);
    const tick = (ts) => {
      if (!start) start = ts;
      const p = Math.min((ts - start) / dur, 1);
      setVal(to * ease(p));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [seen, to, dur]);
  const fmt = (v) => v.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
  return <span ref={ref} className={className} style={style}>{prefix}{fmt(val)}{suffix}</span>;
}

/* lightweight parallax — translateY based on scroll progress through viewport */
function useParallax(strength = 0.12) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;if (!el) return;
    let raf = 0;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        const r = el.getBoundingClientRect();
        const vh = window.innerHeight;
        const prog = (r.top + r.height / 2 - vh / 2) / vh; // -1..1 around center
        const m = getComputedStyle(document.documentElement).getPropertyValue("--motion") || 1;
        el.style.transform = `translate3d(0,${(-prog * strength * 100 * parseFloat(m)).toFixed(2)}px,0)`;
      });
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {window.removeEventListener("scroll", onScroll);window.removeEventListener("resize", onScroll);};
  }, [strength]);
  return ref;
}

/* brand mark + wordmark */
function Logo({ on = "light", size = 30, wordmark = true }) {
  const inv = on === "dark";
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 11 }}>
      <img src="assets/sopisafe-mark.svg" alt=""
      style={{ height: size, width: Math.round(size * 562 / 857), filter: inv ? "invert(1)" : "none", flexShrink: 0 }} />
      {wordmark && <span style={{ fontFamily: "var(--display)", fontWeight: 600, fontSize: size * 0.74,
        letterSpacing: "-.02em", color: inv ? "var(--on-dark)" : "var(--ink)" }}>SopiSafe</span>}
    </span>);

}

function Arrow({ d = "ur" }) {
  // up-right arrow glyph
  return <svg className="arw" width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
    <path d="M3 11L11 3M11 3H4.5M11 3V9.5" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
  </svg>;
}

function Btn({ variant = "primary", children, href, arrow = true, onClick }) {
  const isButton = onClick && (!href || href === "#");
  const Tag = isButton ? "button" : "a";
  const props = isButton
    ? { type: "button", onClick }
    : { href: href || "#" };
    
  return (
    <Tag {...props} className={`btn btn-${variant}`} style={{ display: "inline-flex", justifyContent: "space-between", alignItems: "center", textAlign: "right", gap: "90px" }}>
      {children}{arrow && <Arrow />}
    </Tag>
  );
}

function Mono({ children, style = {} }) {
  return <span style={{ fontFamily: "var(--mono)", ...style }}>{children}</span>;
}

/* hamburger toggle — hidden on wide screens, shown via media query (.nav-burger),
   except `always` instances (e.g. the close button inside MobileMenu) */
function NavBurger({ open, onClick, color = "var(--ink)", border = "var(--line)", always = false }) {
  return (
    <button className={always ? "" : "nav-burger"} type="button" onClick={onClick}
      aria-label={open ? "Close menu" : "Open menu"} aria-expanded={open}
      style={{ display: always ? "flex" : "none", flexDirection: "column", justifyContent: "center", alignItems: "center",
        gap: 5, width: 42, height: 42, padding: 0, flexShrink: 0,
        background: "none", border: `1px solid ${border}`, borderRadius: 12, cursor: "pointer" }}>
      <span aria-hidden="true" style={{ width: 16, height: 1.8, borderRadius: 2, background: color,
        transition: "transform .3s ease", transform: open ? "translateY(3.4px) rotate(45deg)" : "none" }} />
      <span aria-hidden="true" style={{ width: 16, height: 1.8, borderRadius: 2, background: color,
        transition: "transform .3s ease", transform: open ? "translateY(-3.4px) rotate(-45deg)" : "none" }} />
    </button>
  );
}

/* full-screen menu for narrow viewports */
function MobileMenu({ open, onClose, links, ctaLabel, onAudit, signinLabel, langSwitch }) {
  useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);
  useEffect(() => {
    if (!open) return;
    const onKey = e => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div role="dialog" aria-modal="true" aria-label="Menu"
      style={{ position: "fixed", inset: 0, zIndex: 50, background: "var(--paper)",
        display: "flex", flexDirection: "column", animation: "fade .25s ease" }}>
      <div className="wrap" style={{ width: "100%", height: 78, flexShrink: 0,
        display: "flex", alignItems: "center", justifyContent: "space-between" }}>
        <Logo size={30} />
        <NavBurger always open onClick={onClose} />
      </div>
      <nav className="wrap" style={{ width: "100%", flexGrow: 1, overflowY: "auto",
        display: "flex", flexDirection: "column", gap: 4, paddingTop: "clamp(18px,6vh,48px)" }}>
        {links.map(([label, href], i) => (
          <a key={href} href={href} onClick={onClose}
            style={{ fontFamily: "var(--display)", fontWeight: 600, letterSpacing: "-.02em",
              fontSize: "clamp(32px,7.5vw,46px)", lineHeight: 1.2, color: "var(--ink)", width: "fit-content",
              animation: `menu-in .55s cubic-bezier(.2,.7,.2,1) ${80 + i * 60}ms both` }}>
            {label}
          </a>
        ))}
      </nav>
      <div className="wrap" style={{ width: "100%", flexShrink: 0, display: "grid", gap: 20, paddingBlock: 28,
        animation: `menu-in .55s cubic-bezier(.2,.7,.2,1) ${140 + links.length * 60}ms both` }}>
        {langSwitch}
        <div style={{ display: "flex", gap: 16, alignItems: "center", flexWrap: "wrap" }}>
          <Btn variant="primary" arrow={false} onClick={() => { onClose(); if (onAudit) onAudit(); }}>{ctaLabel}</Btn>
          {signinLabel && <a href="#" onClick={onClose} style={{ fontWeight: 600, fontSize: 15.5, color: "var(--ink)" }}>{signinLabel}</a>}
        </div>
      </div>
    </div>
  );
}

/* ---------------------------------------------------------------- LANG CONTEXT */
const LangContext = React.createContext({ lang: "en", setLang: () => {}, t: (k) => k });
function useLang() { return React.useContext(LangContext); }

Object.assign(window, { useInView, Reveal, ClipLines, Counter, useParallax, Logo, Arrow, Btn, Mono, NavBurger, MobileMenu, LangContext, useLang });