/**
 * useSelection Hook
 *
 * A composable hook for managing multi-select state with support for
 * single selection, range selection (shift+click), and select all.
 *
 * @example
 * ```tsx
 * interface User { id: number; name: string; }
 *
 * const selection = useSelection<User>({
 *   keyExtractor: (user) => user.id,
 * });
 *
 * return (
 *   <div>
 *     <input
 *       type="checkbox"
 *       checked={selection.isAllSelected}
 *       ref={(el) => el && (el.indeterminate = selection.isIndeterminate)}
 *       onChange={() => selection.toggleAll(users)}
 *     />
 *     {users.map(user => (
 *       <div key={user.id}>
 *         <input
 *           type="checkbox"
 *           checked={selection.isSelected(user)}
 *           onChange={() => selection.toggle(user)}
 *         />
 *         {user.name}
 *       </div>
 *     ))}
 *   </div>
 * );
 * ```
 *
 * ⚠️ AUTO-GENERATED BY PRISM - DO NOT EDIT
 */

import { useState, useCallback, useMemo, useRef } from 'react';
import type {
  SelectionOptions,
  SelectionState,
  SelectionActions,
  UseSelectionResult,
} from '../types';

/**
 * Default key extractor that uses 'id' property or the item itself.
 */
function defaultKeyExtractor<T>(item: T): string | number {
  if (typeof item === 'object' && item !== null && 'id' in item) {
    return (item as { id: string | number }).id;
  }
  if (typeof item === 'string' || typeof item === 'number') {
    return item;
  }
  return JSON.stringify(item);
}

/**
 * Hook for managing multi-selection state.
 *
 * @param options - Selection options
 * @returns Selection state and actions
 */
export function useSelection<T>(
  options: SelectionOptions<T> = {}
): UseSelectionResult<T> {
  const {
    initialSelected = [],
    keyExtractor = defaultKeyExtractor,
    maxSelections,
  } = options;

  const [selected, setSelected] = useState<T[]>(initialSelected);
  const lastSelectedRef = useRef<T | null>(null);

  // Build a set of selected keys for O(1) lookup
  const selectedKeys = useMemo(
    () => new Set(selected.map(keyExtractor)),
    [selected, keyExtractor]
  );

  // Computed state
  const count = selected.length;
  const isEmpty = count === 0;

  // These need the total items to be meaningful, so we track against the last toggleAll call
  const totalItemsRef = useRef<number>(0);
  const isAllSelected = totalItemsRef.current > 0 && count === totalItemsRef.current;
  const isIndeterminate = count > 0 && count < totalItemsRef.current;

  // Actions
  const isSelected = useCallback(
    (item: T): boolean => selectedKeys.has(keyExtractor(item)),
    [selectedKeys, keyExtractor]
  );

  const select = useCallback(
    (item: T) => {
      const key = keyExtractor(item);
      if (selectedKeys.has(key)) {
        return; // Already selected
      }
      if (maxSelections !== undefined && selected.length >= maxSelections) {
        return; // Max selections reached
      }
      setSelected((prev) => [...prev, item]);
      lastSelectedRef.current = item;
    },
    [keyExtractor, selectedKeys, selected.length, maxSelections]
  );

  const deselect = useCallback(
    (item: T) => {
      const key = keyExtractor(item);
      if (!selectedKeys.has(key)) {
        return; // Not selected
      }
      setSelected((prev) => prev.filter((s) => keyExtractor(s) !== key));
    },
    [keyExtractor, selectedKeys]
  );

  const toggle = useCallback(
    (item: T) => {
      const key = keyExtractor(item);
      if (selectedKeys.has(key)) {
        deselect(item);
      } else {
        select(item);
      }
    },
    [keyExtractor, selectedKeys, select, deselect]
  );

  const selectAll = useCallback(
    (items: T[]) => {
      totalItemsRef.current = items.length;
      if (maxSelections !== undefined) {
        setSelected(items.slice(0, maxSelections));
      } else {
        setSelected([...items]);
      }
    },
    [maxSelections]
  );

  const deselectAll = useCallback(() => {
    setSelected([]);
  }, []);

  const toggleAll = useCallback(
    (items: T[]) => {
      totalItemsRef.current = items.length;
      if (selected.length === items.length) {
        // All selected, deselect all
        setSelected([]);
      } else {
        // Some or none selected, select all
        if (maxSelections !== undefined) {
          setSelected(items.slice(0, maxSelections));
        } else {
          setSelected([...items]);
        }
      }
    },
    [selected.length, maxSelections]
  );

  const selectRange = useCallback(
    (items: T[], start: T, end: T) => {
      const startKey = keyExtractor(start);
      const endKey = keyExtractor(end);

      let startIndex = -1;
      let endIndex = -1;

      items.forEach((item, index) => {
        const key = keyExtractor(item);
        if (key === startKey) startIndex = index;
        if (key === endKey) endIndex = index;
      });

      if (startIndex === -1 || endIndex === -1) {
        return; // Items not found
      }

      // Ensure startIndex <= endIndex
      if (startIndex > endIndex) {
        [startIndex, endIndex] = [endIndex, startIndex];
      }

      const rangeItems = items.slice(startIndex, endIndex + 1);

      setSelected((prev) => {
        const existingKeys = new Set(prev.map(keyExtractor));
        const newItems = rangeItems.filter(
          (item) => !existingKeys.has(keyExtractor(item))
        );
        const combined = [...prev, ...newItems];
        if (maxSelections !== undefined) {
          return combined.slice(0, maxSelections);
        }
        return combined;
      });
    },
    [keyExtractor, maxSelections]
  );

  const reset = useCallback(() => {
    setSelected(initialSelected);
    totalItemsRef.current = 0;
    lastSelectedRef.current = null;
  }, [initialSelected]);

  // Build state object
  const state: SelectionState<T> = {
    selected,
    selectedKeys,
    count,
    isEmpty,
    isAllSelected,
    isIndeterminate,
  };

  // Build actions object
  const actions: SelectionActions<T> = {
    select,
    deselect,
    toggle,
    selectAll,
    deselectAll,
    toggleAll,
    isSelected,
    selectRange,
    reset,
  };

  return {
    ...state,
    ...actions,
  };
}

export default useSelection;
