/* ============================================================
   lib.jsx — Icon, scroll hooks, shared primitives → window.UI
   ============================================================ */
const { useRef, useLayoutEffect, useState: useS, useEffect: useE } = React;

/* ---- Lucide icon: render an empty span, inject converted SVG imperatively.
   React never reconciles the SVG (it only owns the empty span), so language
   swaps and icon changes are safe. ---- */
function Icon({ name, className = "", style, strokeWidth }) {
  const ref = useRef(null);
  useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;
    el.innerHTML = `<i data-lucide="${name}" class="${className}"></i>`;
    if (window.lucide) {
      try { window.lucide.createIcons({ nameAttr: "data-lucide" }); } catch (e) {}
    }
    const svg = el.querySelector("svg");
    if (svg && strokeWidth) svg.setAttribute("stroke-width", strokeWidth);
  }, [name, className, strokeWidth]);
  return React.createElement("span", { ref, className: "lu-ico", style, "aria-hidden": "true" });
}

/* ---- Reveal-on-scroll wrapper ---- */
function Reveal({ children, className = "", delay = 0, x = false, as = "div", style = {} }) {
  const ref = useRef(null);
  const [seen, setSeen] = useS(false);
  useE(() => {
    const el = ref.current;
    if (!el) return;
    let done = false;
    const reveal = () => { if (!done) { done = true; setSeen(true); } };
    const inView = () => {
      const r = el.getBoundingClientRect();
      return r.top < (window.innerHeight || 800) * 0.96 && r.bottom > 0;
    };
    // 1) already on screen at mount
    if (inView()) { reveal(); return; }
    // 2) IntersectionObserver (preferred for stagger)
    let io;
    try {
      io = new IntersectionObserver(
        (entries) => entries.forEach((e) => { if (e.isIntersecting) { reveal(); io && io.disconnect(); } }),
        { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
      );
      io.observe(el);
    } catch (e) {}
    // 3) scroll fallback (fires even if IO doesn't in this environment)
    const onScroll = () => { if (inView()) { reveal(); cleanup(); } };
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    // 4) safety net — never leave content hidden
    const safety = setTimeout(reveal, 1400);
    function cleanup() {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (io) io.disconnect();
      clearTimeout(safety);
    }
    return cleanup;
  }, []);
  return React.createElement(
    as,
    {
      ref,
      className: `reveal ${x ? "reveal-x" : ""} ${seen ? "in" : ""} ${className}`,
      style: { transitionDelay: `${delay}ms`, ...style },
    },
    children
  );
}

/* ---- Scroll progress across a tall section (0 at top hits viewport top,
   1 when its bottom reaches viewport bottom). Used by the day-story. ---- */
function useScrollProgress(ref) {
  const [p, setP] = useS(0);
  useE(() => {
    let raf = 0;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        const el = ref.current;
        if (!el) return;
        const rect = el.getBoundingClientRect();
        const vh = window.innerHeight;
        const total = rect.height - vh;
        const scrolled = -rect.top;
        const prog = total > 0 ? scrolled / total : 0;
        setP(Math.max(0, Math.min(1, prog)));
      });
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [ref]);
  return p;
}

/* ---- Count-up when in view ---- */
function useCountUp(target, run, dur = 1100) {
  const [v, setV] = useS(0);
  useE(() => {
    if (!run) return;
    let raf = 0, start = 0;
    const tick = (ts) => {
      if (!start) start = ts;
      const k = Math.min(1, (ts - start) / dur);
      const eased = 1 - Math.pow(1 - k, 3);
      setV(Math.round(target * eased));
      if (k < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [run, target, dur]);
  return v;
}

/* ---- Primitives ---- */
function Eyebrow({ children, light }) {
  return React.createElement(
    "p",
    { className: `inline-flex items-center gap-2 text-sm font-semibold mb-3 ${light ? "text-teal" : "text-brand"}` },
    React.createElement("span", { className: `h-1 w-1 rounded-full ${light ? "bg-teal" : "bg-brand"}` }),
    children
  );
}

function Tag({ children, color }) {
  const map = {
    brand: "bg-brand-soft text-brand border-brand/20",
    teal: "bg-teal-soft text-teal border-teal/20",
    destructive: "bg-destructive/10 text-destructive border-destructive/20",
    slate: "bg-muted text-slate border-border",
  };
  return React.createElement(
    "span",
    { className: `text-[11px] px-2 py-1 rounded-full border font-medium whitespace-nowrap ${map[color] || map.slate}` },
    children
  );
}

/* Section heading block */
function SectionHead({ eye, title, sub, light, center, max = "max-w-3xl" }) {
  return React.createElement(
    "div",
    { className: `${max} ${center ? "mx-auto text-center" : ""} mb-12` },
    React.createElement(Reveal, null,
      React.createElement(Eyebrow, { light }, eye),
      React.createElement("h2", { className: `text-4xl sm:text-5xl track-tight ${light ? "text-navy-foreground" : "text-navy"}` }, title),
      sub ? React.createElement("p", { className: `mt-5 text-lg text-pretty ${light ? "text-navy-foreground/70" : "text-slate"}` }, sub) : null
    )
  );
}

window.UI = { Icon, Reveal, useScrollProgress, useCountUp, Eyebrow, Tag, SectionHead };
