// Microinteractions library — button hover, reveal, count-up, metrics overlay

// ── Responsive viewport hook ──────────────────────────────────────────────
// Mobile-first breakpoints. Inline-styled components branch on these flags
// (e.g. `gridTemplateColumns: isMobile ? '1fr' : 'repeat(3,1fr)'`).
// Init from window.innerWidth so the first client paint is already correct
// (the app is client-only — no SSR — so there's no hydration mismatch).
const BP = { mobile: 640, tablet: 1024 };
window.BP = BP;

function readViewport() {
  const w = (typeof window !== 'undefined' && window.innerWidth) || 1280;
  return {
    width: w,
    isMobile: w < BP.mobile,
    isTablet: w >= BP.mobile && w < BP.tablet,
    isDesktop: w >= BP.tablet,
  };
}

function useViewport() {
  const [vp, setVp] = React.useState(readViewport);
  React.useEffect(() => {
    let raf = 0;
    const onResize = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => setVp(readViewport()));
    };
    window.addEventListener('resize', onResize);
    window.addEventListener('orientationchange', onResize);
    return () => {
      window.removeEventListener('resize', onResize);
      window.removeEventListener('orientationchange', onResize);
      cancelAnimationFrame(raf);
    };
  }, []);
  return vp;
}
window.useViewport = useViewport;

// ── Hash router ────────────────────────────────────────────────────────────
// The site is multi-page via hash routes: `#/`, `#/chi-siamo`, `#/speaker`,
// `#/partner`, `#/biglietti`. This needs zero server config and survives
// refresh/deep-links on any static host. Plain in-page anchors (e.g. #cosa,
// #contatti) do NOT start with "/", so they scroll natively without changing
// the page — that's how a route and an on-page jump coexist in one hash.
function currentRoute() {
  const raw = ((typeof window !== 'undefined' && window.location.hash) || '').replace(/^#/, '');
  if (!raw.startsWith('/')) return null;            // in-page anchor → not a route change
  const p = raw.replace(/\/+$/, '');
  return p === '' ? '/' : p;
}

function useRoute() {
  const [path, setPath] = React.useState(() => currentRoute() || '/');
  React.useEffect(() => {
    const onHash = () => {
      const r = currentRoute();
      if (r) { setPath(r); window.scrollTo(0, 0); }  // new page → reset to top
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  return path;
}

// Programmatic navigation (e.g. redirect away from a route the phase hides).
function navigate(path) {
  const p = path.startsWith('/') ? path : '/' + path;
  window.location.hash = '#' + p;
}

// Smooth-scroll to an element on the current page without touching the route.
function scrollToId(id, e) {
  if (e) e.preventDefault();
  const el = document.getElementById(id);
  if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}

window.currentRoute = currentRoute;
window.useRoute = useRoute;
window.navigate = navigate;
window.scrollToId = scrollToId;

// Button hover wrapper — fixed lift on hover (CSS .mag:hover), NOT cursor-relative.
// Keeps onMagnet firing on enter so interaction metrics still count.
function Magnetic({ children, onMagnet, style, strength, ...rest }) {
  return (
    <div className="mag" style={{ display: 'inline-block', ...style }}
      onMouseEnter={() => { if (onMagnet) onMagnet(); }} {...rest}>
      {children}
    </div>
  );
}

// Reveal on scroll — IntersectionObserver fade-up; reports section seen
function Reveal({ children, sectionId, onSeen, style, delay = 0, as: Tag = 'div', ...rest }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((en) => {
        if (en.isIntersecting) {
          setTimeout(() => el.classList.add('in'), delay);
          if (sectionId && onSeen) onSeen(sectionId);
          io.disconnect();
        }
      });
      // threshold 0 (fire on any intersection) so sections taller than the
      // viewport still reveal — a ratio threshold can be unreachable on mobile
      // where a whole page stacks into one very tall column, leaving it blank.
      // rootMargin pulls the trigger slightly inside the viewport for feel.
    }, { threshold: 0, rootMargin: '0px 0px -10% 0px' });
    io.observe(el);
    return () => io.disconnect();
  }, [sectionId, onSeen, delay]);
  return <Tag ref={ref} className="reveal" style={style} {...rest}>{children}</Tag>;
}

// Count-up number (animates when scrolled into view)
function CountUp({ to, suffix = '', duration = 1200, format = (n) => Math.round(n).toLocaleString('it-IT') }) {
  const ref = React.useRef(null);
  const [val, setVal] = React.useState(0);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((en) => {
        if (en.isIntersecting) {
          const start = performance.now();
          const step = (now) => {
            const t = Math.min(1, (now - start) / duration);
            const eased = 1 - Math.pow(1 - t, 3);
            setVal(eased * to);
            if (t < 1) requestAnimationFrame(step);
            else setVal(to);
          };
          requestAnimationFrame(step);
          io.disconnect();
        }
      });
    }, { threshold: 0.4 });
    io.observe(el);
    return () => io.disconnect();
  }, [to, duration]);
  return <span ref={ref} className="counter-box">{format(val)}{suffix}</span>;
}

// Metrics overlay — live readout of session signals
function MetricsOverlay({ metrics, phase }) {
  const [, force] = React.useReducer(x => x + 1, 0);
  React.useEffect(() => {
    const start = performance.now();
    let raf;
    const tick = () => {
      metrics.dwellMs = performance.now() - start;
      const max = document.documentElement.scrollHeight - window.innerHeight;
      metrics.scroll = window.scrollY;
      metrics.depth = max > 0 ? Math.min(1, window.scrollY / max) : 0;
      force();
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [metrics]);

  const sec = Math.floor(metrics.dwellMs / 1000);
  const sectionsCount = metrics.sectionsSeen ? metrics.sectionsSeen.size : 0;

  return (
    <div className="metrics" aria-hidden="true">
      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6, opacity: .7 }}>
        <span>● LIVE METRICS</span>
        <span>{phase === 'public' ? 'EVENTO' : 'PRE-VENDITA'}</span>
      </div>
      <div>Dwell&nbsp;&nbsp;<b>{sec}s</b></div>
      <div>Depth&nbsp;&nbsp;<b>{Math.round(metrics.depth * 100)}%</b><span className="metrics-bar"><i style={{ width: `${metrics.depth * 100}%` }}/></span></div>
      <div>Sections&nbsp;<b>{sectionsCount}/6</b></div>
      <div>Hovers&nbsp;<b>{metrics.hovers || 0}</b></div>
      <div>Magnets&nbsp;<b>{metrics.magnets || 0}</b></div>
      <div>Clicks&nbsp;&nbsp;<b>{metrics.clicks || 0}</b></div>
    </div>
  );
}

// Tiny toast notifier
function useToast() {
  const [msg, setMsg] = React.useState(null);
  const show = React.useCallback((text) => {
    setMsg(text);
    setTimeout(() => setMsg(null), 2400);
  }, []);
  const node = msg ? <div className="toast show">{msg}</div> : null;
  return [show, node];
}

window.useViewport = useViewport;
window.Magnetic = Magnetic;
window.Reveal = Reveal;
window.CountUp = CountUp;
window.MetricsOverlay = MetricsOverlay;
window.useToast = useToast;
