// createVisibilityObserver, IntersectionObserver-compatible helper with a
// getBoundingClientRect fallback for environments where native IO delivers an
// initial batch but never fires again (e.g. some embedded iframes).
// Both native IO entries and rect-fallback checks are routed through the same
// change-gate (lastState), so duplicates are deduped and the fallback keeps
// running for the observer's whole lifetime.
// API: observe(el), unobserve(el), disconnect(), takeRecords().
function createVisibilityObserver(callback, options = {}) {
  const rawThreshold = Array.isArray(options.threshold)
    ? (options.threshold[0] || 0)
    : (options.threshold || 0);

  let target = null;
  let lastState = null;
  let disconnected = false;
  let intervalId = null;

  const emit = (isIntersecting, ratio, el) => {
    if (disconnected) return;
    if (isIntersecting === lastState) return;
    lastState = isIntersecting;
    callback([{ isIntersecting, intersectionRatio: ratio, target: el }]);
  };

  let io = null;
  if (typeof IntersectionObserver !== 'undefined') {
    try {
      io = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          emit(entry.isIntersecting, entry.intersectionRatio, entry.target);
        });
      }, options);
    } catch (e) {
      io = null;
    }
  }

  const check = () => {
    if (disconnected || !target) return;
    const r = target.getBoundingClientRect();
    const vh = window.innerHeight || document.documentElement.clientHeight;
    if (r.height <= 0) return;
    const visibleH = Math.min(r.bottom, vh) - Math.max(r.top, 0);
    const ratio = Math.max(0, visibleH) / r.height;
    // If the element is taller than the viewport, the requested threshold may
    // be unreachable, clamp it to what is physically possible.
    const effThreshold = Math.min(rawThreshold, Math.max(0, (vh / r.height) * 0.9));
    const isIntersecting = visibleH > 0 && ratio >= effThreshold;
    emit(isIntersecting, ratio, target);
  };

  const startFallback = () => {
    window.addEventListener('scroll', check, { passive: true });
    window.addEventListener('resize', check);
    intervalId = setInterval(check, 300);
    setTimeout(check, 50);
  };

  const stopFallback = () => {
    window.removeEventListener('scroll', check);
    window.removeEventListener('resize', check);
    if (intervalId !== null) { clearInterval(intervalId); intervalId = null; }
  };

  return {
    observe(el) {
      target = el;
      if (io) io.observe(el);
      startFallback();
    },
    unobserve(el) {
      if (io) io.unobserve(el);
      if (el === target) target = null;
    },
    disconnect() {
      disconnected = true;
      stopFallback();
      if (io) io.disconnect();
    },
    takeRecords() {
      return io ? io.takeRecords() : [];
    }
  };
}

Object.assign(window, { createVisibilityObserver });
