/* components.jsx — shared primitives, icons, hooks */
const { useState, useEffect, useRef, useCallback, useMemo } = React;

/* ───────────────────────── Hooks ───────────────────────── */

// Reveal-on-scroll: returns a ref + a boolean `seen`.
// Robust by design: IntersectionObserver + scroll/resize listeners + an absolute
// safety timer, so an element can NEVER stay hidden even if one mechanism fails.
function useInView(opts = {}) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let done = false;
    let io = null;
    const cleanup = () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      document.removeEventListener("scroll", onScroll, true);
      if (io) io.disconnect();
    };
    const reveal = () => { if (done) return; done = true; setSeen(true); cleanup(); };
    const check = () => {
      if (done || !ref.current) return;
      const r = ref.current.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight || 800;
      if (r.top < vh * 0.9 && r.bottom > 0) reveal();
    };
    const onScroll = () => check();

    if (typeof IntersectionObserver !== "undefined") {
      io = new IntersectionObserver((entries) => {
        entries.forEach((e) => { if (e.isIntersecting) reveal(); });
      }, { rootMargin: "0px 0px -8% 0px", threshold: 0.01 });
      io.observe(el);
    }
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    document.addEventListener("scroll", onScroll, true); // catch scrollable ancestors

    check();
    const t1 = setTimeout(check, 100);
    const t2 = setTimeout(check, 400);
    // absolute safety net: nothing stays hidden past this, regardless of triggers
    const tSafe = setTimeout(reveal, 2200);
    return () => { clearTimeout(t1); clearTimeout(t2); clearTimeout(tSafe); cleanup(); };
  }, []);
  return [ref, seen];
}

// Wrapper that reveals its children with an optional stagger delay.
// IMPORTANT: opacity is driven statically by `seen` (no transition/animation on
// it) so visibility never depends on an animation timeline advancing — some
// embedding contexts freeze the timeline, which would otherwise park a fade at
// opacity 0 forever. The translateY rise is decorative only (transform transition);
// if the timeline is frozen the element just sits at its start offset, still fully
// visible at opacity 1.
function Reveal({ children, delay = 0, as = "div", className = "", style = {}, ...rest }) {
  const [ref, seen] = useInView();
  const Tag = as;
  return (
    <Tag
      ref={ref}
      className={`reveal ${seen ? "reveal--in" : ""} ${className}`}
      style={{
        opacity: seen ? 1 : 0,
        transform: seen ? "none" : "translateY(22px)",
        transition: `transform 700ms var(--ease) ${delay}ms`,
        ...style,
      }}
      {...rest}
    >
      {children}
    </Tag>
  );
}

// Count from 0 → target when scrolled into view.
// rAF for smooth motion + a setTimeout safety that forces the final value, so the
// number is correct even where the animation timeline is frozen.
function useCountUp(target, { duration = 1500, decimals = 0, start = true } = {}) {
  const [val, setVal] = useState(0);
  const raf = useRef(0);
  useEffect(() => {
    if (!start) return;
    let t0 = null, done = false;
    const ease = (p) => 1 - Math.pow(1 - p, 3);
    const tick = (t) => {
      if (done) return;
      if (t0 === null) t0 = t;
      const p = Math.min(1, (t - t0) / duration);
      setVal(target * ease(p));
      if (p < 1) raf.current = requestAnimationFrame(tick);
    };
    raf.current = requestAnimationFrame(tick);
    const safety = setTimeout(() => { done = true; cancelAnimationFrame(raf.current); setVal(target); }, duration + 350);
    return () => { done = true; cancelAnimationFrame(raf.current); clearTimeout(safety); };
  }, [target, duration, start]);
  return decimals ? val.toFixed(decimals) : Math.round(val);
}

// Eased 0→1 progress while `active`. rAF for smoothness, with a setTimeout safety
// that guarantees it reaches 1 even when the animation timeline is frozen.
function useProgress(active, duration = 1200) {
  const [p, setP] = useState(0);
  useEffect(() => {
    if (!active) return;
    let raf = 0, t0 = null, done = false;
    const ease = (x) => 1 - Math.pow(1 - x, 3);
    const tick = (t) => {
      if (done) return;
      if (t0 === null) t0 = t;
      const k = Math.min(1, (t - t0) / duration);
      setP(ease(k));
      if (k < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    const safety = setTimeout(() => { done = true; cancelAnimationFrame(raf); setP(1); }, duration + 350);
    return () => { done = true; cancelAnimationFrame(raf); clearTimeout(safety); };
  }, [active, duration]);
  return p;
}

function Stat({ value, prefix = "", suffix = "", decimals = 0, label, sub }) {
  const [ref, seen] = useInView({ threshold: 0.4 });
  const n = useCountUp(value, { decimals, start: seen });
  return (
    <div ref={ref} className="stat">
      <div className="stat-n">
        {prefix}<span className="tabnum">{n}</span>{suffix}
      </div>
      <div className="stat-label">{label}</div>
      {sub && <div className="stat-sub">{sub}</div>}
    </div>
  );
}

/* ───────────────────────── Icons (stroke, currentColor) ───────────────────────── */
const I = {
  cycle: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M21 12a9 9 0 1 1-2.64-6.36M21 4v4h-4" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  match: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M4 7h7M4 7l3-3M4 7l3 3M20 17h-7M20 17l-3-3M20 17l-3 3" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  chart: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M4 19V5M4 19h16M8 19v-6M12 19V9M16 19v-9M20 19v-4" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  shield: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M12 3l7 3v5c0 4.5-3 8-7 10-4-2-7-5.5-7-10V6l7-3z" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round"/><path d="M9 12l2 2 4-4" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  network: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><circle cx="6" cy="6" r="2.4" stroke="currentColor" strokeWidth="1.7"/><circle cx="18" cy="6" r="2.4" stroke="currentColor" strokeWidth="1.7"/><circle cx="12" cy="18" r="2.4" stroke="currentColor" strokeWidth="1.7"/><path d="M7.7 7.7L11 15.5M16.3 7.7L13 15.5M8 6h8" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"/></svg>),
  bolt: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M13 2L4 14h7l-1 8 9-12h-7l1-8z" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round"/></svg>),
  check: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M5 12.5l4.5 4.5L19 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  x: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/></svg>),
  arrow: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  lock: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><rect x="5" y="11" width="14" height="9" rx="2" stroke="currentColor" strokeWidth="1.7"/><path d="M8 11V8a4 4 0 0 1 8 0v3" stroke="currentColor" strokeWidth="1.7"/></svg>),
  doc: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M7 3h7l4 4v14H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round"/><path d="M14 3v4h4M9 13h6M9 17h6" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"/></svg>),
  search: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><circle cx="11" cy="11" r="6" stroke="currentColor" strokeWidth="1.7"/><path d="M20 20l-4-4" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"/></svg>),
  upload: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M12 16V5M8 9l4-4 4 4M5 19h14" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  send: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M21 3L10 14M21 3l-7 18-4-7-7-4 18-7z" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round"/></svg>),
  pulse: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M3 12h4l2-6 4 12 2-6h6" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  wallet: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><rect x="3" y="6" width="18" height="13" rx="2.5" stroke="currentColor" strokeWidth="1.7"/><path d="M3 10h18M16 14h2" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"/></svg>),
  clock: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.7"/><path d="M12 7v5l3.5 2" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>),
  spark: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M12 3v4M12 17v4M5 12H1M23 12h-4M6.3 6.3L3.5 3.5M20.5 20.5l-2.8-2.8M17.7 6.3l2.8-2.8M3.5 20.5l2.8-2.8" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>),
  menu: (p) => (<svg viewBox="0 0 24 24" fill="none" {...p}><path d="M4 7h16M4 12h16M4 17h16" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/></svg>),
};

/* ───────────────────────── Logo ───────────────────────── */
function LogoMark({ size = 34 }) {
  // Recreates the bracket + D mark from the brand logo as crisp SVG.
  const id = useMemo(() => "lg" + Math.random().toString(36).slice(2, 7), []);
  return (
    <svg width={size} height={size} viewBox="0 0 100 100" fill="none" aria-hidden="true">
      <defs>
        <linearGradient id={id} x1="20" y1="12" x2="80" y2="88" gradientUnits="userSpaceOnUse">
          <stop stopColor="#16CFBD" /><stop offset="0.5" stopColor="#2FB1F0" /><stop offset="1" stopColor="#5C6CF2" />
        </linearGradient>
      </defs>
      <path d="M30 20 H78 V80 H40" stroke={`url(#${id})`} strokeWidth="5" strokeLinecap="round" fill="none"/>
      <path d="M22 20 V44" stroke={`url(#${id})`} strokeWidth="5" strokeLinecap="round"/>
      <path d="M46 34 V66 H56 C64 66 68 60 68 50 C68 40 64 34 56 34 H46 Z" stroke={`url(#${id})`} strokeWidth="5" strokeLinejoin="round" fill="none"/>
    </svg>
  );
}

function Wordmark({ compact = false }) {
  return (
    <div className="brand">
      <LogoMark size={32} />
      <div className="brand-tx">
        <span className="brand-name">DATAMED</span>
        {!compact && <span className="brand-sub">ABA Claims Automation</span>}
      </div>
    </div>
  );
}

/* Small UI atoms */
function Pill({ children, tone = "accent" }) {
  return <span className={`pill pill-${tone}`}>{children}</span>;
}

function IconBadge({ icon, tone = "brand", size = 52 }) {
  const Ico = I[icon];
  return (
    <div className={`ibadge ibadge-${tone}`} style={{ width: size, height: size }}>
      <Ico style={{ width: size * 0.46, height: size * 0.46 }} />
    </div>
  );
}

Object.assign(window, {
  useInView, Reveal, useCountUp, useProgress, Stat,
  I, LogoMark, Wordmark, Pill, IconBadge,
});
