import { RefObject, useEffect, useRef } from 'react';
import { IMedia } from '../types';
import { useStore } from '../services/store';

/**
 * Minimum fraction of the thumbnail that must be visible in the viewport
 * before it is counted as "viewed".
 *
 * 0.5 (50 %) is intentional: carousel shoulder thumbnails are only partially
 * visible (typically < 30 %), so they are excluded automatically.
 */
const VISIBILITY_THRESHOLD = 0.5;

/**
 * Module-level registry that prevents a `thumbnailViewed` event from firing
 * more than once for the same media within the same widget instance.
 *
 * Keyed by widgetId → Set of media IDs that have already fired.
 * This also handles infinite-carousel clones, which share the same media.id
 * as the real slide.
 */
const viewedByWidget = new Map<string, Set<string>>();

function getOrCreateViewedSet(widgetId: string): Set<string> {
  if (!viewedByWidget.has(widgetId)) {
    viewedByWidget.set(widgetId, new Set());
  }
  return viewedByWidget.get(widgetId)!;
}

/**
 * Observes `elementRef` and fires a single `thumbnailViewed` analytics event
 * the first time the thumbnail is visible to the user.
 *
 * Two strategies are used:
 * 
 * 1. Wall / Mosaic / Mobile carousel — IntersectionObserver (viewport).
 *    Items are only rendered/visible when scrolled into view, so the observer
 *    fires only for genuinely visible thumbnails.
 *
 * 2. Desktop carousel : A thumbnail is counted as viewed when its postIndex 
 *    falls within the current visible window [currentPosition, currentPosition + contentSize). 
 *    This effect re-runs on every arrow navigation. (We cannot use IntersectionObserver here because of the carousel loop cloning technique)
 */
export function useThumbnailViewEvent(
  elementRef: RefObject<HTMLElement | null>,
  media: IMedia,
  position: number,
): void {
  const store = useStore();
  const triggeredRef = useRef(false);
  const observerRef = useRef<IntersectionObserver | undefined>(undefined);

  const widgetId = store.data.id;
  const triggerEvent = store.triggerEvent;
  const isDesktopCarousel = store.data.settings.type === 'carousel' && !store.isMobile;
  const contentSize = Math.max(store.data.settings.content_size ?? 1, 1);
  const currentPosition = store.currentPosition;

  // ── Strategy 1: desktop carousel — position-based ────────────────────────
  useEffect(() => {
    if (!isDesktopCarousel) return;
    if (triggeredRef.current) return;

    const el = elementRef.current;
    if (!el) return;
    if (el.closest('[data-carousel-clone="true"]')) return;

    const viewedSet = getOrCreateViewedSet(widgetId);

    if (viewedSet.has(media.id)) {
      triggeredRef.current = true;
      return;
    }

    const isInVisibleWindow =
      media.postIndex >= currentPosition &&
      media.postIndex < currentPosition + contentSize;

    if (isInVisibleWindow) {
      triggeredRef.current = true;
      viewedSet.add(media.id);
      triggerEvent('thumbnailViewed', { post: media, position }, widgetId);
    }
    // Re-run when the arrow moves the carousel to a new position.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentPosition, isDesktopCarousel]);

  // ── Strategy 2: wall / mosaic / mobile carousel — IntersectionObserver ───
  useEffect(() => {
    if (isDesktopCarousel) return;

    const el = elementRef.current;
    if (!el) return;
    if (el.closest('[data-carousel-clone="true"]')) return;

    const viewedSet = getOrCreateViewedSet(widgetId);

    if (viewedSet.has(media.id)) {
      triggeredRef.current = true;
      return;
    }

    observerRef.current = new IntersectionObserver(
      (entries) => {
        const [entry] = entries;
        if (entry?.isIntersecting && !triggeredRef.current) {
          triggeredRef.current = true;
          viewedSet.add(media.id);
          triggerEvent('thumbnailViewed', { post: media, position }, widgetId);
          observerRef.current?.disconnect();
        }
      },
      {
        root: null, // viewport
        threshold: VISIBILITY_THRESHOLD,
      },
    );

    observerRef.current.observe(el);

    return () => {
      observerRef.current?.disconnect();
    };
    // Only mount once — triggeredRef / viewedSet guard against duplicates.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isDesktopCarousel]);
}

