import { useCallback, useRef, useEffect } from 'react';
import { useMutation } from 'react-query';

import { logEvent } from 'src/config/logging';
import {
  VIEW_TIME_FOR_POST_METRIC,
  VISIBLE_PERCENT_FOR_POST_METRIC,
} from 'src/constants/posts';
import { setGroupFeedListData } from 'src/pages/dashboard/groups/queries';
import { setGroupPostViewed } from 'src/services/groups';
import { setPostViewed } from 'src/services/posts';
import { EventName } from 'src/types/amplitude';

export type ViewTrackingContext = 'feed' | 'groups' | 'marketplace';

type PostMetadataForView = {
  id: number;
  groupId?: number;
  /**
   * If the post is a share, the reference to the original post. When set,
   * marking the container as viewed also marks the original as viewed
   * (per share-post spec).
   */
  original?: { id: number; groupId?: number };
};

type UsePostsViewTrackingProps = {
  posts: PostMetadataForView[];
  userId?: number;
  instanceId?: number;
  /** Context determines behavior: feed/groups call API + analytics, marketplace only logs analytics */
  context?: ViewTrackingContext;
};

/**
 * Hook to track post viewed status and analytics for Feed, Groups, and Marketplace.
 * Uses a single IntersectionObserver to track all posts efficiently.
 *
 * - Feed/Groups: Marks posts as viewed via API + logs analytics
 * - Marketplace: Only logs analytics (no server-side viewed state)
 *
 * Posts are marked as viewed after VIEW_TIME_FOR_POST_METRIC ms of being
 * at least VISIBLE_PERCENT_FOR_POST_METRIC visible.
 */
const usePostsViewTracking = ({
  posts,
  userId,
  instanceId,
  context = 'feed',
}: UsePostsViewTrackingProps) => {
  const visiblePostsRef = useRef<Set<number>>(new Set());
  const viewStartTimesRef = useRef<Map<number, number>>(new Map());
  const processedPostsRef = useRef<Set<number>>(new Set());
  const observerRef = useRef<IntersectionObserver | null>(null);
  const postsRef = useRef(posts);
  postsRef.current = posts;

  const isMarketplace = context === 'marketplace';

  // Log analytics based on context
  const logViewEvent = useCallback(
    (post: PostMetadataForView) => {
      if (isMarketplace) {
        logEvent(EventName.POST_MARKETPLACE_VIEW, { postId: post.id });
      } else if (post.groupId) {
        logEvent(EventName.GROUPS_POST_VIEWED, {
          postId: post.id,
          groupId: post.groupId,
          userId,
          instanceId,
        });
      } else {
        logEvent(EventName.POST_FEED_VIEW, { postId: post.id });
      }
    },
    [isMarketplace, userId, instanceId],
  );

  // Single mutation for marking posts as viewed (feed/groups only)
  const markAsViewedMutation = useMutation(
    async (_posts: PostMetadataForView[]) => {
      // Marketplace doesn't have a server-side viewed API
      if (isMarketplace) return;

      const promises = _posts.flatMap(post => {
        const containerCall = post.groupId
          ? setGroupPostViewed(post.id, post.groupId)
          : setPostViewed(post.id);

        if (!post.original) return [containerCall];

        const originalCall = post.original.groupId
          ? setGroupPostViewed(post.original.id, post.original.groupId)
          : setPostViewed(post.original.id);

        return [containerCall, originalCall.catch(() => null)];
      });
      return Promise.all(promises);
    },
    {
      onSuccess: (_, _posts) => {
        _posts.forEach(post => {
          if (!isMarketplace && post.groupId) {
            // Update group post cache to mark as viewed
            setGroupFeedListData(post.id, String(post.groupId), oldPost => {
              if (!oldPost) return;
              oldPost.viewed = true;
              return oldPost;
            });
          }
          logViewEvent(post);
        });
      },
    },
  );

  // Process visible posts that have been viewed for VIEW_TIME_FOR_POST_METRIC ms
  const processVisiblePosts = useCallback(() => {
    const now = Date.now();
    const postsToProcess: PostMetadataForView[] = [];

    // Check which posts have been visible long enough and haven't been processed
    viewStartTimesRef.current.forEach((startTime, postId) => {
      if (
        now - startTime >= VIEW_TIME_FOR_POST_METRIC &&
        !processedPostsRef.current.has(postId) &&
        visiblePostsRef.current.has(postId)
      ) {
        const post = postsRef.current.find(p => p.id === postId);
        if (post) {
          postsToProcess.push({
            id: postId,
            groupId: post.groupId,
            original: post.original,
          });
          processedPostsRef.current.add(postId);
        }
      }
    });

    if (postsToProcess.length > 0) {
      if (isMarketplace) {
        postsToProcess.forEach(logViewEvent);
      } else {
        markAsViewedMutation.mutate(postsToProcess);
      }
    }
  }, [markAsViewedMutation, isMarketplace, logViewEvent]);

  const postIdsKey = posts.map(p => p.id).join(',');

  // Initialize intersection observer
  useEffect(() => {
    if (observerRef.current) {
      observerRef.current.disconnect();
    }

    observerRef.current = new IntersectionObserver(
      entries => {
        entries.forEach(entry => {
          const postId = parseInt(
            entry.target.getAttribute('data-post-id') || '0',
          );

          if (entry.isIntersecting) {
            // Post became visible
            visiblePostsRef.current.add(postId);
            if (!viewStartTimesRef.current.has(postId)) {
              viewStartTimesRef.current.set(postId, Date.now());
            }
          } else {
            // Post is no longer visible
            visiblePostsRef.current.delete(postId);
            viewStartTimesRef.current.delete(postId);
          }
        });
      },
      {
        threshold: VISIBLE_PERCENT_FOR_POST_METRIC,
        rootMargin: '50px', // Start tracking 50px before the post enters viewport
      },
    );

    // Observe all post elements
    const postElements = document.querySelectorAll('[data-post-id]');
    postElements.forEach(el => observerRef.current?.observe(el));

    return () => {
      if (observerRef.current) {
        observerRef.current.disconnect();
      }
    };
  }, [postIdsKey]); // Re-initialize when posts change

  // Process visible posts periodically
  useEffect(() => {
    const interval = setInterval(processVisiblePosts, 2000);
    return () => clearInterval(interval);
  }, [processVisiblePosts]);

  // Cleanup processed posts when posts change (for infinite scroll)
  useEffect(() => {
    const currentPostIds = new Set(postsRef.current.map(post => post.id));

    // Remove tracking data for posts that are no longer in the list
    Array.from(processedPostsRef.current).forEach(postId => {
      if (!currentPostIds.has(postId)) {
        processedPostsRef.current.delete(postId);
        visiblePostsRef.current.delete(postId);
        viewStartTimesRef.current.delete(postId);
      }
    });
  }, [postIdsKey]);

  return {
    isMarkingAsViewed: markAsViewedMutation.isLoading,
    visiblePostsCount: visiblePostsRef.current.size,
  };
};

export default usePostsViewTracking;
