// lp-reveal.jsx — scroll-reveal + count-up + draw-on-mount helpers.
// Pure CSS transitions driven by IntersectionObserver. No deps.

(function injectRevealStyles() {
  if (typeof document === "undefined") return;
  if (document.getElementById("lp-reveal-styles")) return;
  const s = document.createElement("style");
  s.id = "lp-reveal-styles";
  s.textContent = `
    .lp-rv {
      opacity: 0;
      transform: translateY(24px);
      transition: opacity .9s cubic-bezier(.2,.7,.2,1), transform .9s cubic-bezier(.2,.7,.2,1);
      will-change: opacity, transform;
    }
    .lp-rv.lp-rv--in {
      opacity: 1;
      transform: none;
    }
    .lp-rv--right { transform: translateX(40px); }
    .lp-rv--right.lp-rv--in { transform: none; }
    .lp-rv--left { transform: translateX(-40px); }
    .lp-rv--left.lp-rv--in { transform: none; }
    .lp-rv--up-strong { transform: translateY(56px); }
    .lp-rv--up-strong.lp-rv--in { transform: none; }
    .lp-rv--scale { transform: translateY(24px) scale(.96); transform-origin: center bottom; }
    .lp-rv--scale.lp-rv--in { transform: none; }

    /* SVG path draw animation */
    .lp-draw {
      stroke-dasharray: var(--len, 1000);
      stroke-dashoffset: var(--len, 1000);
      transition: stroke-dashoffset 1.8s cubic-bezier(.4,.1,.3,1);
    }
    .lp-draw.lp-draw--in { stroke-dashoffset: 0; }

    /* Pulsing dot for "live" indicator */
    @keyframes lp-pulse {
      0%, 100% { opacity: 1; transform: scale(1); }
      50%      { opacity: 0.4; transform: scale(0.7); }
    }
    .lp-pulse { animation: lp-pulse 2s ease-in-out infinite; }

    /* Subtle floating for hero pill */
    @keyframes lp-float {
      0%, 100% { transform: translateY(0); }
      50%      { transform: translateY(-3px); }
    }
    .lp-float { animation: lp-float 4s ease-in-out infinite; }

    /* Bar chart bar grow */
    @keyframes lp-bar-grow {
      from { transform: scaleY(0); }
      to   { transform: scaleY(1); }
    }
    .lp-bar { transform-origin: bottom; }
    .lp-bar.lp-bar--in {
      animation: lp-bar-grow .9s cubic-bezier(.3,.7,.3,1) backwards;
    }

    /* Calendar cell slide-in */
    @keyframes lp-cell-in {
      from { transform: scaleX(0); opacity: 0; }
      to   { transform: scaleX(1); opacity: 1; }
    }
    .lp-cell { transform-origin: left center; }
    .lp-cell.lp-cell--in {
      animation: lp-cell-in .6s cubic-bezier(.3,.7,.3,1) backwards;
    }

    /* Respect reduced motion */
    @media (prefers-reduced-motion: reduce) {
      .lp-rv, .lp-rv.lp-rv--in,
      .lp-draw, .lp-draw.lp-draw--in,
      .lp-bar.lp-bar--in, .lp-cell.lp-cell--in,
      .lp-pulse, .lp-float {
        opacity: 1 !important;
        transform: none !important;
        stroke-dashoffset: 0 !important;
        animation: none !important;
        transition: none !important;
      }
    }
  `;
  document.head.appendChild(s);
})();

// ───────────────────────────────────────────────────────────
// Hook: useInView — fires once when element enters viewport.
// ───────────────────────────────────────────────────────────
function useInView(opts) {
  const { rootMargin = "0px 0px -10% 0px", threshold = 0.1, once = true } = opts || {};
  const ref = React.useRef(null);
  const [inView, setInView] = React.useState(false);

  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (typeof IntersectionObserver === "undefined") { setInView(true); return; }
    const io = new IntersectionObserver(
      (entries) => {
        for (const e of entries) {
          if (e.isIntersecting) {
            setInView(true);
            if (once) io.disconnect();
          } else if (!once) setInView(false);
        }
      },
      { rootMargin, threshold }
    );
    io.observe(el);
    return () => io.disconnect();
  }, [rootMargin, threshold, once]);

  return [ref, inView];
}

// ───────────────────────────────────────────────────────────
// <Reveal> — wraps children, slides + fades in on enter.
//   direction: "up" (default) | "left" | "right" | "up-strong" | "scale"
//   delay: ms
//   as: html tag (default "div")
// ───────────────────────────────────────────────────────────
function Reveal({ children, direction = "up", delay = 0, as = "div", className = "", style = {}, ...rest }) {
  const [ref, inView] = useInView();
  const Tag = as;
  const cls = [
    "lp-rv",
    direction !== "up" && `lp-rv--${direction}`,
    inView && "lp-rv--in",
    className,
  ].filter(Boolean).join(" ");
  return (
    <Tag
      ref={ref}
      className={cls}
      style={{ ...style, transitionDelay: delay ? `${delay}ms` : undefined }}
      {...rest}
    >
      {children}
    </Tag>
  );
}

// Stagger a list of children with incremental delay
function Stagger({ children, step = 80, start = 0, direction = "up", as = "div", style = {}, ...rest }) {
  const kids = React.Children.toArray(children);
  return (
    <>
      {kids.map((c, i) => (
        <Reveal key={c.key ?? i} direction={direction} delay={start + i * step} as={as} style={style} {...rest}>
          {c}
        </Reveal>
      ))}
    </>
  );
}

// ───────────────────────────────────────────────────────────
// <CountUp> — animates a number from 0 to target when in view.
// Handles "−80%", "+30%", "14 dni", "24 / 7", "€4 218" by extracting the
// numeric token and tweening it; surrounding string is preserved.
// ───────────────────────────────────────────────────────────
function CountUp({ value, duration = 1400, className, style }) {
  const [ref, inView] = useInView();
  const [display, setDisplay] = React.useState(value);
  const startedRef = React.useRef(false);

  React.useEffect(() => {
    if (!inView || startedRef.current) return;
    startedRef.current = true;

    // Find the first numeric token
    const match = String(value).match(/(-?\d[\d  ]*)/);
    if (!match) { setDisplay(value); return; }
    const raw = match[1];
    const target = parseInt(raw.replace(/[\s ]/g, ""), 10);
    if (!Number.isFinite(target)) { setDisplay(value); return; }

    const start = performance.now();
    const ease = (t) => 1 - Math.pow(1 - t, 3);

    const fmt = (n) => {
      // Re-insert thousands separator if original had a space
      const hadSpace = /\d\s\d/.test(raw);
      let s = Math.round(n).toString();
      if (hadSpace) s = s.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
      return value.replace(raw, s);
    };

    let raf;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      setDisplay(fmt(target * ease(t)));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView, value, duration]);

  return <span ref={ref} className={className} style={style}>{display}</span>;
}

// ───────────────────────────────────────────────────────────
// <DrawPath> — SVG path that strokes itself when in view.
// Wraps a <path> element; auto-measures length on mount.
// ───────────────────────────────────────────────────────────
function DrawPath({ d, stroke, strokeWidth = 1.5, strokeOpacity = 1, fill = "none", delay = 0, duration = 1800, ...rest }) {
  const [ref, inView] = useInView({ threshold: 0.2 });
  const [len, setLen] = React.useState(2000);
  const setRef = React.useCallback((el) => {
    ref.current = el;
    if (el && el.getTotalLength) {
      try { setLen(el.getTotalLength()); } catch {}
    }
  }, [ref]);
  return (
    <path
      ref={setRef}
      d={d}
      stroke={stroke}
      strokeWidth={strokeWidth}
      strokeOpacity={strokeOpacity}
      fill={fill}
      strokeLinecap="round"
      className={`lp-draw${inView ? " lp-draw--in" : ""}`}
      style={{ "--len": len, transitionDelay: delay ? `${delay}ms` : undefined, transitionDuration: `${duration}ms` }}
      {...rest}
    />
  );
}

window.useInView = useInView;
window.Reveal = Reveal;
window.Stagger = Stagger;
window.CountUp = CountUp;
window.DrawPath = DrawPath;
