import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react'
import { Field } from '../../../../types'
import { cn } from '@/lib/utils'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Button } from '@/components/ui/button'
import { Check, ChevronsUpDown } from 'lucide-react'
import { Badge } from '@/components/ui/badge'

// Define a type for field options
interface FieldOption {
  label: string;
  value: string;
}

interface MultiSelectCellProps<T extends string> {
  field: Field<T>
  value: string[]
  onChange: (value: string[]) => void
  onStartEdit?: () => void
  onEndEdit?: () => void
  hasErrors?: boolean
  options?: readonly FieldOption[]
  disabled?: boolean
  className?: string
}

// Memoized option item to prevent unnecessary renders for large option lists
const OptionItem = React.memo(({
  option,
  isSelected,
  onSelect
}: {
  option: FieldOption,
  isSelected: boolean,
  onSelect: (value: string) => void
}) => (
  <CommandItem
    key={option.value}
    value={option.value}
    onSelect={() => onSelect(option.value)}
    className="flex w-full"
  >
    <div className="flex items-center w-full overflow-hidden">
      <Check
        className={cn(
          "mr-2 h-4 w-4 flex-shrink-0",
          isSelected ? "opacity-100" : "opacity-0"
        )}
      />
      <span className="truncate w-full">{option.label}</span>
    </div>
  </CommandItem>
), (prev, next) => {
  return prev.option.value === next.option.value && 
         prev.isSelected === next.isSelected;
});

OptionItem.displayName = 'OptionItem';

// Create a virtualized list component for large option lists
const VirtualizedOptions = React.memo(({
  options,
  selectedValues,
  onSelect,
  maxHeight = 200
}: {
  options: FieldOption[],
  selectedValues: Set<string>,
  onSelect: (value: string) => void,
  maxHeight?: number
}) => {
  const listRef = useRef<HTMLDivElement>(null);
  
  // Only render visible options for better performance with large lists
  const [visibleOptions, setVisibleOptions] = useState<FieldOption[]>([]);
  const [scrollPosition, setScrollPosition] = useState(0);
  
  // Constants for virtualization
  const itemHeight = 32; // Height of each option item in pixels
  const visibleCount = Math.ceil(maxHeight / itemHeight) + 2; // Number of visible items + buffer
  
  // Handle scroll events
  const handleScroll = useCallback(() => {
    if (listRef.current) {
      setScrollPosition(listRef.current.scrollTop);
    }
  }, []);
  
  // Update visible options based on scroll position
  useEffect(() => {
    if (options.length <= visibleCount) {
      // If fewer options than visible count, just show all
      setVisibleOptions(options);
      return;
    }
    
    // Calculate start and end indices
    const startIndex = Math.floor(scrollPosition / itemHeight);
    const endIndex = Math.min(startIndex + visibleCount, options.length);
    
    // Update visible options
    setVisibleOptions(options.slice(Math.max(0, startIndex), endIndex));
  }, [options, scrollPosition, visibleCount, itemHeight]);
  
  // If fewer than the threshold, render all directly
  if (options.length <= 100) {
    return (
      <div ref={listRef} className="max-h-[200px] overflow-y-auto" onScroll={handleScroll}>
        {options.map(option => (
          <OptionItem
            key={option.value}
            option={option}
            isSelected={selectedValues.has(option.value)}
            onSelect={onSelect}
          />
        ))}
      </div>
    );
  }
  
  return (
    <div 
      ref={listRef} 
      className="max-h-[200px] overflow-y-auto" 
      onScroll={handleScroll} 
      style={{ height: `${Math.min(maxHeight, options.length * itemHeight)}px` }}
    >
      <div style={{ height: `${options.length * itemHeight}px`, position: 'relative' }}>
        <div style={{ 
          position: 'absolute', 
          top: `${Math.floor(scrollPosition / itemHeight) * itemHeight}px`,
          width: '100%'
        }}>
          {visibleOptions.map(option => (
            <OptionItem
              key={option.value}
              option={option}
              isSelected={selectedValues.has(option.value)}
              onSelect={onSelect}
            />
          ))}
        </div>
      </div>
    </div>
  );
});

VirtualizedOptions.displayName = 'VirtualizedOptions';

const MultiSelectCell = <T extends string>({
  field,
  value = [],
  onChange,
  onStartEdit,
  onEndEdit,
  hasErrors,
  options: providedOptions,
  disabled = false,
  className = ''
}: MultiSelectCellProps<T>) => {
  const [open, setOpen] = useState(false)
  const [searchQuery, setSearchQuery] = useState("")
  // Add internal state for tracking selections - ensure value is always an array
  const [internalValue, setInternalValue] = useState<string[]>(Array.isArray(value) ? value : [])
  // Ref for the command list to enable scrolling
  const commandListRef = useRef<HTMLDivElement>(null)
  // Add state for hover
  const [isHovered, setIsHovered] = useState(false)
  // Add ref to track if we need to sync internal state with external value
  const shouldSyncWithExternalValue = useRef(true)

  // Create a memoized Set for fast lookups of selected values
  const selectedValueSet = useMemo(() => new Set(internalValue), [internalValue]);

  // Sync internalValue with external value when component mounts or value changes externally
  // Modified to prevent infinite loop by checking if values are different before updating
  useEffect(() => {
    // Only sync if we should (not during internal edits) and if not open
    if (shouldSyncWithExternalValue.current && !open) {
      const externalValue = Array.isArray(value) ? value : [];
      
      // Only update if values are actually different to prevent infinite loops
      if (internalValue.length !== externalValue.length || 
          !internalValue.every(v => externalValue.includes(v)) ||
          !externalValue.every(v => internalValue.includes(v))) {
        setInternalValue(externalValue);
      }
    }
  }, [value, open, internalValue]);

  // Handle open state changes with improved responsiveness
  const handleOpenChange = useCallback((newOpen: boolean) => {
    if (open && !newOpen) {
      // Prevent syncing with external value during our internal update
      shouldSyncWithExternalValue.current = false;
      
      // Only update parent state when dropdown closes
      // Make a defensive copy to avoid mutations
      const valuesToCommit = [...internalValue];
      
      // Immediate UI update
      setOpen(false);
      
      // Update parent with the value immediately
      onChange(valuesToCommit);
      if (onEndEdit) onEndEdit();
      
      // Allow syncing with external value again after a short delay
      setTimeout(() => {
        shouldSyncWithExternalValue.current = true;
      }, 0);
    } else if (newOpen && !open) {
      // When opening the dropdown, sync with external value
      const externalValue = Array.isArray(value) ? value : [];
      setInternalValue(externalValue);
      setSearchQuery(""); // Reset search query on open
      setOpen(true);
      if (onStartEdit) onStartEdit();
    } else if (!newOpen) {
      // Handle case when dropdown is already closed but handleOpenChange is called
      setOpen(false);
    }
  }, [open, internalValue, value, onChange, onStartEdit, onEndEdit]);

  // Memoize field options to prevent unnecessary recalculations
  const selectOptions = useMemo(() => {
    const fieldType = field.fieldType;
    const fieldOptions = fieldType && 
      (fieldType.type === 'select' || fieldType.type === 'multi-select') && 
      fieldType.options ? 
      fieldType.options : 
      [];
    
    // Use provided options or field options, ensuring they have the correct shape
    // Skip this work if we have a large number of options and they didn't change
    if (providedOptions && providedOptions.length > 0) {
      // Check if options are already in the right format
      if (typeof providedOptions[0] === 'object' && 'label' in providedOptions[0] && 'value' in providedOptions[0]) {
        return providedOptions as FieldOption[];
      }
      
      return providedOptions.map(option => ({
        label: option.label || String(option.value),
        value: String(option.value)
      }));
    }
    
    // Check field options format
    if (fieldOptions.length > 0) {
      if (typeof fieldOptions[0] === 'object' && 'label' in fieldOptions[0] && 'value' in fieldOptions[0]) {
        return fieldOptions as FieldOption[];
      }
      
      return fieldOptions.map(option => ({
        label: option.label || String(option.value),
        value: String(option.value)
      }));
    }
    
    // Add default option if no options available
    return [{ label: 'No options available', value: '' }];
  }, [field.fieldType, providedOptions]);
  
  // Use deferredValue for search to prevent UI blocking with large lists
  const deferredSearchQuery = React.useDeferredValue(searchQuery);
  
  // Memoize filtered options based on search query - efficient filtering algorithm
  const filteredOptions = useMemo(() => {
    // If no search query, return all options
    if (!deferredSearchQuery.trim()) return selectOptions;
    
    const query = deferredSearchQuery.toLowerCase();
    
    // Use faster algorithm for large option lists
    if (selectOptions.length > 100) {
      return selectOptions.filter(option => {
        // First check starting with the query (most relevant)
        if (option.label.toLowerCase().startsWith(query)) return true;
        
        // Then check includes for more general matches
        return option.label.toLowerCase().includes(query);
      });
    }

    // For smaller lists, do full text search
    return selectOptions.filter(option =>
      option.label.toLowerCase().includes(query)
    );
  }, [selectOptions, deferredSearchQuery]);

  // Sort options with selected items at the top for the dropdown - only for smaller lists
  const sortedOptions = useMemo(() => {
    // Skip expensive sorting for large lists
    if (selectOptions.length > 100) return filteredOptions;
    
    return [...filteredOptions].sort((a, b) => {
      const aSelected = selectedValueSet.has(a.value);
      const bSelected = selectedValueSet.has(b.value);
      
      if (aSelected && !bSelected) return -1;
      if (!aSelected && bSelected) return 1;
      return a.label.localeCompare(b.label);
    });
  }, [filteredOptions, selectedValueSet, selectOptions.length]);

  // Memoize selected values display
  const selectedValues = useMemo(() => {
    // Use a map for looking up options by value for better performance
    const optionsMap = new Map(selectOptions.map(opt => [opt.value, opt]));
    
    return internalValue.map(v => {
      const option = optionsMap.get(v);
      return {
        value: v,
        label: option ? option.label : String(v)
      };
    });
  }, [internalValue, selectOptions]);

  // Update the handleSelect to operate on internalValue instead of directly calling onChange
  const handleSelect = useCallback((selectedValue: string) => {
    // Prevent syncing with external value during our internal update
    shouldSyncWithExternalValue.current = false;
    
    setInternalValue(prev => {
      let newValue;
      if (prev.includes(selectedValue)) {
        // Remove the value
        newValue = prev.filter(v => v !== selectedValue);
      } else {
        // Add the value - make a new array to avoid mutations
        newValue = [...prev, selectedValue];
      }
      return newValue;
    });
    
    // Allow syncing with external value again after a short delay
    setTimeout(() => {
      shouldSyncWithExternalValue.current = true;
    }, 0);
  }, []);

  // Handle wheel scroll in dropdown
  const handleWheel = useCallback((e: React.WheelEvent) => {
    if (commandListRef.current) {
      e.stopPropagation();
      commandListRef.current.scrollTop += e.deltaY;
    }
  }, []);
  
  // Helper function to check if a class is present in the className string
  const hasClass = (cls: string): boolean => {
    const classNames = className.split(' ');
    return classNames.includes(cls);
  };

  // If disabled, just render the value without any interactivity
  if (disabled) {
    const displayValue = internalValue.length > 0 
      ? internalValue.map(val => {
          const option = selectOptions.find(opt => opt.value === val);
          return option ? option.label : val;
        }).join(', ')
      : '';
    
    return (
      <div 
        className={cn(
          "w-full px-3 py-2 h-10 rounded-md text-sm flex items-center",
          "border",
          hasErrors ? "border-destructive" : "border-input",
          className
        )}
        style={{
          backgroundColor: hasClass('!bg-blue-100') ? '#dbeafe' : 
                          hasClass('!bg-blue-200') ? '#bfdbfe' : 
                          hasClass('hover:!bg-blue-100') && isHovered ? '#dbeafe' :
                          undefined,
          borderColor: hasClass('!border-blue-500') ? '#3b82f6' : 
                      hasClass('!border-blue-200') ? '#bfdbfe' : 
                      hasClass('!border-blue-200') && isHovered ? '#bfdbfe' :
                      undefined,
          borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined,
          cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined
        }}
        onMouseEnter={() => setIsHovered(true)}
        onMouseLeave={() => setIsHovered(false)}
      >
        {displayValue || ""}
      </div>
    );
  }

  return (
    <Popover open={open} onOpenChange={(o) => {
      // Only open the popover if we're not in copy down mode
      if (!hasClass('!bg-blue-100') && !hasClass('!bg-blue-200') && !hasClass('hover:!bg-blue-100')) {
        setOpen(o);
        handleOpenChange(o);
      }
    }}>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          aria-expanded={open}
          className={cn(
            "w-full justify-between font-normal",
            "border",
            !internalValue.length && "text-muted-foreground",
            hasErrors ? "border-destructive" : "",
            className
          )}
          style={{
            backgroundColor: hasClass('!bg-blue-100') ? '#dbeafe' : 
                            hasClass('!bg-blue-200') ? '#bfdbfe' : 
                            hasClass('hover:!bg-blue-100') && isHovered ? '#dbeafe' :
                            undefined,
            borderColor: hasClass('!border-blue-500') ? '#3b82f6' : 
                        hasClass('!border-blue-200') ? '#bfdbfe' : 
                        hasClass('!border-blue-200') && isHovered ? '#bfdbfe' :
                        undefined,
            borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined,
            borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined,
            cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined
          }}
          onClick={(e) => {
            // Don't open the dropdown if we're in copy down mode
            if (hasClass('!bg-blue-100') || hasClass('!bg-blue-200') || hasClass('hover:!bg-blue-100')) {
              // Let the parent cell handle the click by NOT preventing default or stopping propagation
              return;
            }
            
            // Only prevent default and stop propagation if not in copy down mode
            e.preventDefault();
            e.stopPropagation();
            
            // Toggle the open state and call handleOpenChange to ensure values are saved
            const newOpenState = !open;
            setOpen(newOpenState);
            handleOpenChange(newOpenState);
          }}
          onMouseEnter={() => setIsHovered(true)}
          onMouseLeave={() => setIsHovered(false)}
        >
          <div className="flex items-center w-full justify-between">
            <div className="flex items-center gap-2 overflow-hidden">
              {internalValue.length === 0 ? (
                <span className="text-muted-foreground truncate w-full">Select...</span>
              ) : internalValue.length === 1 ? (
                <span className="truncate w-full">{selectedValues[0].label}</span>
              ) : (
                <>
                  <Badge variant="secondary" className="shrink-0 whitespace-nowrap">
                    {internalValue.length} selected
                  </Badge>
                  <span className="truncate">
                    {selectedValues.map(v => v.label).join(', ')}
                  </span>
                </>
              )}
            </div>
            <ChevronsUpDown className="mx-2 h-4 w-4 shrink-0 opacity-50" />
          </div>
        </Button>
      </PopoverTrigger>
      <PopoverContent 
        className="p-0 w-[var(--radix-popover-trigger-width)]" 
        align="start"
        sideOffset={4}
      >
        <Command shouldFilter={false}>
          <CommandInput 
            placeholder="Search..."
            className="h-9"
            value={searchQuery}
            onValueChange={setSearchQuery}
          />
          <CommandList 
            ref={commandListRef} 
            onWheel={handleWheel}
            className="max-h-[200px]"
          >
            <CommandEmpty>No results found.</CommandEmpty>
            <CommandGroup>
              {sortedOptions.map((option) => (
                <CommandItem
                  key={option.value}
                  value={option.label}
                  onSelect={() => handleSelect(option.value)}
                  className="cursor-pointer"
                >
                  {option.label}
                  {selectedValueSet.has(option.value) && (
                    <Check className="ml-auto h-4 w-4" />
                  )}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
};

MultiSelectCell.displayName = 'MultiSelectCell';

export default React.memo(MultiSelectCell, (prev, next) => {
  // Check primitive props first (cheap comparisons)
  if (prev.hasErrors !== next.hasErrors) return false;
  if (prev.disabled !== next.disabled) return false;
  if (prev.className !== next.className) return false;
  
  // Check field reference
  if (prev.field !== next.field) return false;
  
  // Check value arrays (potentially expensive for large arrays)
  // Handle undefined or null values safely
  const prevValue = prev.value || [];
  const nextValue = next.value || [];
  
  if (prevValue.length !== nextValue.length) return false;
  for (let i = 0; i < prevValue.length; i++) {
    if (prevValue[i] !== nextValue[i]) return false;
  }
  
  // Check options (potentially expensive for large option lists)
  const prevOptions = prev.options || [];
  const nextOptions = next.options || [];
  if (prevOptions.length !== nextOptions.length) return false;
  
  // For large option lists, just compare references
  if (prevOptions.length > 100) {
    return prevOptions === nextOptions;
  }
  
  // For smaller lists, do a shallow comparison
  for (let i = 0; i < prevOptions.length; i++) {
    if (prevOptions[i] !== nextOptions[i]) return false;
  }
  
  return true;
}); 