import {useCallback, useRef} from 'react';

import {useReplayContext} from 'sentry/components/replays/replayContext';
import {relativeTimeInMs} from 'sentry/components/replays/utils';
import {BreadcrumbType, Crumb} from 'sentry/types/breadcrumbs';
import useActiveReplayTab from 'sentry/utils/replays/hooks/useActiveReplayTab';
import useOrganization from 'sentry/utils/useOrganization';
import type {NetworkSpan} from 'sentry/views/replays/types';

function useCrumbHandlers(startTimestampMs: number = 0) {
  const organization = useOrganization();
  const {
    clearAllHighlights,
    highlight,
    removeHighlight,
    setCurrentHoverTime,
    setCurrentTime,
  } = useReplayContext();
  const {setActiveTab} = useActiveReplayTab();

  const hasErrorTab = organization.features.includes('session-replay-errors-tab');

  const mouseEnterCallback = useRef<{
    id: string | number | null;
    timeoutId: NodeJS.Timeout | null;
  }>({
    id: null,
    timeoutId: null,
  });

  const handleMouseEnter = useCallback(
    (item: Crumb | NetworkSpan) => {
      // this debounces the mouseEnter callback in unison with mouseLeave
      // we ensure the pointer remains over the target element before dispatching state events in order to minimize unnecessary renders
      // this helps during scrolling or mouse move events which would otherwise fire in rapid succession slowing down our app
      mouseEnterCallback.current.id = item.id;
      mouseEnterCallback.current.timeoutId = setTimeout(() => {
        if (startTimestampMs) {
          setCurrentHoverTime(relativeTimeInMs(item.timestamp ?? '', startTimestampMs));
        }

        if (item.data && typeof item.data === 'object' && 'nodeId' in item.data) {
          // XXX: Kind of hacky, but mouseLeave does not fire if you move from a
          // crumb to a tooltip
          clearAllHighlights();
          highlight({nodeId: item.data.nodeId, annotation: item.data.label});
        }
        mouseEnterCallback.current.id = null;
        mouseEnterCallback.current.timeoutId = null;
      }, 250);
    },
    [setCurrentHoverTime, startTimestampMs, highlight, clearAllHighlights]
  );

  const handleMouseLeave = useCallback(
    (item: Crumb | NetworkSpan) => {
      // if there is a mouseEnter callback queued and we're leaving it we can just cancel the timeout
      if (mouseEnterCallback.current.id === item.id) {
        if (mouseEnterCallback.current.timeoutId) {
          clearTimeout(mouseEnterCallback.current.timeoutId);
        }
        mouseEnterCallback.current.id = null;
        mouseEnterCallback.current.timeoutId = null;
        // since there is no more work to do we just return
        return;
      }

      setCurrentHoverTime(undefined);

      if (item.data && typeof item.data === 'object' && 'nodeId' in item.data) {
        removeHighlight({nodeId: item.data.nodeId});
      }
    },
    [setCurrentHoverTime, removeHighlight]
  );

  const handleClick = useCallback(
    (crumb: Crumb | NetworkSpan) => {
      if (crumb.timestamp !== undefined) {
        setCurrentTime(relativeTimeInMs(crumb.timestamp, startTimestampMs));
      }

      if (
        crumb.data &&
        typeof crumb.data === 'object' &&
        'action' in crumb.data &&
        crumb.data.action === 'largest-contentful-paint'
      ) {
        setActiveTab('dom');
        return;
      }

      if ('type' in crumb) {
        if (hasErrorTab && crumb.type === BreadcrumbType.ERROR) {
          setActiveTab('errors');
          return;
        }

        switch (crumb.type) {
          case BreadcrumbType.NAVIGATION:
            setActiveTab('network');
            break;
          case BreadcrumbType.UI:
            setActiveTab('dom');
            break;
          default:
            setActiveTab('console');
            break;
        }
      }
    },
    [setCurrentTime, startTimestampMs, setActiveTab, hasErrorTab]
  );

  return {
    handleMouseEnter,
    handleMouseLeave,
    handleClick,
  };
}

export default useCrumbHandlers;
