import { useRef, useCallback, useEffect, ReactNode, FC } from 'react';
import { useStore } from '../services/store';

type NodeRef = Element | null;

// Time in millisecond before sending an event
const VISIBILITY_TIME_FOR_EVENT = 100;

// Module-level guard: prevents widgetViewed from firing more than once per
// widget instance, even when React StrictMode double-mounts in development.
const widgetViewedFired = new Set<string>();

interface VisibilityProps {
  /**
   * children must be a function waiting for a ref callback
   * to give to the children element
   */
  children(ref: (node: NodeRef) => void): ReactNode;
}

/* eslint-disable @typescript-eslint/unbound-method */
/* "children" is not a method, it’s a prop containing a function */
const Visibility: FC<VisibilityProps> = ({ children }) => {
  const store = useStore();
  const observerRef = useRef<IntersectionObserver>();
  const timeoutRef = useRef<NodeJS.Timeout>();
  const triggeredRef = useRef(false);
  const childrenRefValue = useRef<NodeRef>(null);

  const handleIntersection = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      const [entry] = entries;
      if (entry) {
        if (!entry.isIntersecting && timeoutRef.current) {
          clearTimeout(timeoutRef.current);
          timeoutRef.current = undefined; // Avoid leaking pending timers
        }

        if (entry.isIntersecting && !triggeredRef.current && !timeoutRef.current) {
          timeoutRef.current = setTimeout(() => {
            if (!widgetViewedFired.has(store.data.id)) {
              widgetViewedFired.add(store.data.id);
              triggeredRef.current = true;
              store.triggerEvent(
                'widgetViewed',
                { layout: store.data.settings.type },
                store.data.id,
              );
            }
            timeoutRef.current = undefined;
          }, VISIBILITY_TIME_FOR_EVENT);
        }
      }
    },
    [],
  );

  const onChildrenRef = useCallback(
    (ref: NodeRef) => {
      if (!observerRef.current) {
        observerRef.current = new IntersectionObserver(handleIntersection, {
          root: null,
          threshold: [1], // 100% of the widget is visible
        });
      }

      if (childrenRefValue.current) {
        observerRef.current.unobserve(childrenRefValue.current);
      }

      if (ref) {
        observerRef.current.observe(ref);
      }

      childrenRefValue.current = ref;
    },
    [handleIntersection],
  );

  useEffect(
    () => () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
        timeoutRef.current = undefined;
      }

      if (observerRef.current && childrenRefValue.current) {
        observerRef.current.unobserve(childrenRefValue.current);
        observerRef.current.disconnect();
      }
    },
    [],
  );

  return children(onChildrenRef);
};

export default Visibility;
