import { useCallback, KeyboardEvent, useRef, RefObject } from 'react';

type TabNavigationOptions<T> = {
  /** whether to include custom option as the last item */
  includeCustom?: boolean;
  /** the value or index of the current selected item */
  currentValue?: T | number;
  /** whether the current selected item is custom */
  isCustomSelected?: boolean;
  /** a function to compare values, used to determine the current selected item */
  compareValue?: (item: T, value: any) => boolean;
  /** whether to allow Tab key navigation */
  enabled?: boolean;
  /** a selector to find navigable elements */
  selector?: string;
  /** an element container reference, limiting the query DOM range to improve performance */
  containerRef?: RefObject<HTMLElement | null>;
  /** the type of the current value, can be 'index' or 'value' */
  valueType?: 'index' | 'value';
};

/**
 * Tab key navigation hook - implement Tab key circular navigation between a set of options
 *
 * @param items an array of options, can be a simple type (string, number) array or an object array
 * @param onSelect callback when an item is selected
 * @param options configuration options
 * @returns an object containing the event handler and control functions
 *
 * @example
 * // simple string array
 * const { handleKeyDown } = useTabNavigation(['10', '20', '50'], handleSelect);
 *
 * // object array
 * const { handleKeyDown } = useTabNavigation(
 *   [{id: 1, name: 'A'}, {id: 2, name: 'B'}],
 *   handleSelect,
 *   { compareValue: (item, value) => item.id === value.id }
 * );
 */
export const useTabNavigation = <T>(
  items: T[],
  onSelect: (item: T | 'custom', index: number) => void,
  options?: TabNavigationOptions<T>
) => {
  const {
    valueType = 'value',
    includeCustom = false,
    currentValue,
    isCustomSelected = false,
    compareValue = (item: T, value: any) => item === value,
    enabled = true,
    selector = '.tab-navigable-card button',
    containerRef,
  } = options || {};

  const hasTabbed = useRef(false);

  const findNavigableElements = useCallback(() => {
    if (containerRef?.current) {
      return containerRef.current.querySelectorAll(selector);
    }
    return document.querySelectorAll(selector);
  }, [containerRef, selector]);

  // get current index
  const determineCurrentIndex = useCallback(() => {
    const allOptions = includeCustom ? [...items, 'custom' as any] : items;
    if (allOptions.length === 0) return -1;

    // if not tabbed, determine start point by currentValue
    if (!hasTabbed.current) {
      if (isCustomSelected && includeCustom) {
        return items.length; // current selected custom option
      }

      if (currentValue !== undefined) {
        if (valueType === 'index' && typeof currentValue === 'number') {
          // if currentValue is index
          return currentValue >= 0 && currentValue < items.length ? currentValue : -1;
        }
        // if currentValue is value, find matched item
        return items.findIndex((item) => compareValue(item, currentValue));
      }
    } else {
      // if tabbed, find current focused element
      const focusedElement = document.activeElement;
      const navigableElements = findNavigableElements();
      for (let i = 0; i < navigableElements.length; i++) {
        if (navigableElements[i] === focusedElement) {
          return i;
        }
      }
      if (includeCustom && navigableElements.length > 0) {
        return navigableElements.length - 1;
      }
    }

    return -1;
  }, [items, includeCustom, isCustomSelected, currentValue, valueType, compareValue, findNavigableElements]);

  // get next index
  const getNextIndex = useCallback(
    (currentIndex: number, isShiftKey: boolean) => {
      const totalOptions = includeCustom ? items.length + 1 : items.length;

      if (currentIndex === -1) {
        return 0; // no current selected item, start from first item
      }

      if (isShiftKey) {
        // Shift+Tab backward
        return currentIndex === 0 ? totalOptions - 1 : currentIndex - 1;
      }
      // Tab next
      return currentIndex === totalOptions - 1 ? 0 : currentIndex + 1;
    },
    [items, includeCustom]
  );

  const handleKeyDown = useCallback(
    (e: KeyboardEvent) => {
      // if navigation is disabled or not Tab key, do not handle event
      if (!enabled || e.key !== 'Tab') return;

      e.preventDefault();
      e.stopPropagation();

      // determine current index and next index
      const currentIndex = determineCurrentIndex();
      const nextIndex = getNextIndex(currentIndex, e.shiftKey);

      // mark as handled Tab event
      hasTabbed.current = true;

      // execute select callback
      const selectedItem = nextIndex === items.length ? 'custom' : items[nextIndex];
      onSelect(selectedItem, nextIndex);

      // focus to next element
      setTimeout(() => {
        const elements = findNavigableElements();
        if (elements[nextIndex]) {
          (elements[nextIndex] as HTMLElement).focus();
        }
      }, 0);
    },
    [items, onSelect, enabled, determineCurrentIndex, getNextIndex, findNavigableElements]
  );

  // reset Tab state method
  const resetTabNavigation = useCallback(() => {
    hasTabbed.current = false;
  }, []);

  return {
    handleKeyDown,
    resetTabNavigation,
    isTabNavigationActive: hasTabbed.current,
  };
};
