import { useEffect, useState } from 'react';
import { type QueryClient, useQueryClient } from 'react-query';

import useFeatureFlag from 'src/hooks/useFeatureFlag';
import {
  type Task,
  type ViewListItem,
} from 'src/pages/dashboard/serviceManagement/types';
import { FeatureFlags } from 'src/types/featureFlags';
import { type Pagination } from 'src/types/services';

import { serviceManagementKeys } from '../queries';

type InfiniteTasksData = {
  pages: Array<{ data: Pagination<Task[]> }>;
};

// Determines whether any task in a cached query result has an unread bubble.
// Handles both paginated (Pagination<Task[]>) and infinite query shapes.
const hasBubbledTask = (data: unknown): boolean => {
  if (!data) return false;

  const items =
    (data as Pagination<Task[]> | undefined)?.items ??
    (data as { data?: Pagination<Task[]> } | undefined)?.data?.items;
  if (Array.isArray(items)) {
    return items.some(task => (task?.bubbleCount ?? 0) > 0);
  }

  const pages = (data as InfiniteTasksData | undefined)?.pages;
  if (Array.isArray(pages)) {
    return pages.some(page =>
      page?.data?.items?.some(task => (task?.bubbleCount ?? 0) > 0),
    );
  }

  return false;
};

const computeBubbledIds = (
  queryClient: QueryClient,
  views: ViewListItem[],
  selectedViewId?: string,
): Set<string> => {
  const result = new Set<string>();
  if (!selectedViewId) return result;

  const cache = queryClient.getQueryCache();
  const tasksAllKey = serviceManagementKeys.tasks.all();
  const activeBubbled = cache
    .findAll({
      predicate: query =>
        tasksAllKey.every((part, i) => query.queryKey[i] === part) &&
        query.getObserversCount() > 0,
    })
    .some(query => hasBubbledTask(query.state.data));

  if (activeBubbled && views.some(view => view.id === selectedViewId)) {
    result.add(selectedViewId);
  }
  return result;
};

// Returns the set of view IDs that should display a red dot in the sidebar.
//
// Responsibility: read the tasks cache and derive dot visibility reactively.
// It does NOT fetch data — React Query handles that. The hook subscribes to
// cache events so that socket-driven invalidations (which update the cache but
// don't trigger React re-renders on their own) are reflected in the sidebar
// without waiting for an unrelated re-render.
//
// The derived Set is stored directly in state (not via a tick counter) to avoid
// tearing in React 18 concurrent mode. queueMicrotask defers the setState out
// of any active render cycle while still running before the next browser paint.
//
// Limitation: only the currently selected view can show a dot, because it's
// the only one whose tasks are guaranteed to be fresh in the cache. Showing
// dots for unvisited views would require a dedicated per-view count endpoint.
const useViewBubbles = (
  views: ViewListItem[],
  selectedViewId?: string,
): Set<string> => {
  const queryClient = useQueryClient();
  const bubblesEnabled = useFeatureFlag(
    FeatureFlags.SERVICE_MANAGEMENT_BUBBLES_ENABLED,
  );
  const [bubbledIds, setBubbledIds] = useState<Set<string>>(() =>
    bubblesEnabled
      ? computeBubbledIds(queryClient, views, selectedViewId)
      : new Set(),
  );

  useEffect(() => {
    if (!bubblesEnabled) return;
    const tasksAllKey = serviceManagementKeys.tasks.all();
    return queryClient.getQueryCache().subscribe(event => {
      const key = event?.query?.queryKey;
      const isTasksQuery =
        !!key && tasksAllKey.every((part, i) => key[i] === part);
      const isDataEvent =
        event?.type === 'queryUpdated' ||
        event?.type === 'observerResultsUpdated';
      if (isTasksQuery && isDataEvent) {
        queueMicrotask(() =>
          setBubbledIds(computeBubbledIds(queryClient, views, selectedViewId)),
        );
      }
    });
  }, [queryClient, views, selectedViewId]);

  return bubbledIds;
};

export default useViewBubbles;
