import { useState, useRef, useEffect, ReactNode } from "react";
import { TextInput, Loader, Paper, Stack, Box, Text } from "@mantine/core";
import { useDebouncedCallback } from "@mantine/hooks";

export interface TypeaheadOption<T = any> {
  id: string;
  data: T;
}

export interface TypeaheadProps<T> {
  onSelect: (option: TypeaheadOption<T>) => void;
  searchFn: (query: string) => Promise<TypeaheadOption<T>[]>;
  renderOption: (option: TypeaheadOption<T>, isSelected?: boolean) => ReactNode;
  format?: (option: TypeaheadOption<T>) => string;
  placeholder?: string;
  debounceMs?: number;
  disabled?: boolean;
  initialValue?: string;
  maxHeight?: number | string;
}

const Typeahead = <T,>({
  onSelect,
  searchFn,
  renderOption,
  format,
  placeholder = "Search...",
  debounceMs = 300,
  disabled = false,
  initialValue = "",
  maxHeight = 200,
}: TypeaheadProps<T>) => {
  const [searchQuery, setSearchQuery] = useState(initialValue);
  const [searchResults, setSearchResults] = useState<TypeaheadOption<T>[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isOpen, setIsOpen] = useState(false);
  const [selectedIndex, setSelectedIndex] = useState(-1);
  const containerRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);

  const performSearch = async (query: string) => {
    setIsLoading(true);
    try {
      const results = await searchFn(query);
      setSearchResults(results);
      setIsOpen(results.length > 0);
      setSelectedIndex(-1);
    } catch (error) {
      console.error('Search failed:', error);
      setSearchResults([]);
      setIsOpen(false);
    } finally {
      setIsLoading(false);
    }
  };

  const debouncedSearch = useDebouncedCallback(performSearch, debounceMs);

  const handleSearchChange = (value: string) => {
    setSearchQuery(value);
    debouncedSearch(value);
  };

  const handleOptionSelect = (option: TypeaheadOption<T>) => {
    onSelect(option);
    const displayValue = format ? format(option) : String(option.data);
    setSearchQuery(displayValue);
    setIsOpen(false);
    setSelectedIndex(-1);
  };

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
        setIsOpen(false);
      }
    };

    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (!isOpen || searchResults.length === 0) return;

    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setSelectedIndex(prev => (prev < searchResults.length - 1 ? prev + 1 : prev));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setSelectedIndex(prev => (prev > 0 ? prev - 1 : prev));
        break;
      case 'Enter':
        e.preventDefault();
        if (selectedIndex >= 0 && searchResults[selectedIndex]) {
          handleOptionSelect(searchResults[selectedIndex]);
        }
        break;
      case 'Escape':
        setIsOpen(false);
        setSelectedIndex(-1);
        break;
    }
  };

  return (
    <Box ref={containerRef} pos="relative" w="100%">
      <TextInput
        ref={inputRef}
        value={searchQuery}
        onChange={(event) => handleSearchChange(event.currentTarget.value)}
        onKeyDown={handleKeyDown}
        onFocus={async () => {
          if (searchResults.length > 0) {
            setIsOpen(true);
            return;
          }
          await performSearch(searchQuery);
        }}
        placeholder={placeholder}
        rightSection={isLoading ? <Loader size="xs" /> : null}
        disabled={disabled}
      />

      {isOpen && (
        <Paper
          shadow="md"
          p={0}
          bd="1px solid var(--mantine-color-dimmed)"
          style={{
            position: 'absolute',
            top: '100%',
            left: 0,
            right: 0,
            zIndex: 9999,
            maxHeight,
            overflowY: 'auto',
            WebkitOverflowScrolling: 'touch',
            touchAction: 'pan-y',
            borderTop: 0,
            borderTopLeftRadius: 0,
            borderTopRightRadius: 0
          }}
          onTouchMove={(e) => e.stopPropagation()}
        >
          {searchResults.length > 0 ? (
            <Stack gap={0}>
              {searchResults.map((option, index) => (
                <Box
                  key={option.id}
                  style={{
                    cursor: 'pointer',
                    backgroundColor: selectedIndex === index ? 'var(--mantine-color-gray-1)' : 'transparent',
                  }}
                  onClick={() => handleOptionSelect(option)}
                  onMouseEnter={() => setSelectedIndex(index)}
                >
                  {renderOption(option, selectedIndex === index)}
                </Box>
              ))}
            </Stack>
          ) : (
            <Box p="md">
              <Text size="sm" c="dimmed" ta="center">
                {searchQuery.trim() ? 'No results found' : 'Start typing to search...'}
              </Text>
            </Box>
          )}
        </Paper>
      )}
    </Box>
  );
};

export default Typeahead;