import variables from 'config/variables';
import { memo, useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { createPortal } from 'react-dom';
import { MdExpandMore, MdCheck, MdRefresh, MdClose } from 'react-icons/md';
import { toast } from 'react-toastify';

import EventBus from 'utils/eventbus';

import './Dropdown.scss';

const Dropdown = memo((props) => {
  const [value, setValue] = useState(localStorage.getItem(props.name) || props.items[0]?.value);
  const [isOpen, setIsOpen] = useState(false);
  const [isClosing, setIsClosing] = useState(false);
  const [focusedIndex, setFocusedIndex] = useState(-1);
  const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0, width: 0 });
  const [searchQuery, setSearchQuery] = useState('');
  const containerRef = useRef(null);
  const controlRef = useRef(null);
  const menuRef = useRef(null);
  const optionsRef = useRef([]);
  const searchInputRef = useRef(null);

  const closeDropdown = useCallback(() => {
    setIsClosing(true);
    setTimeout(() => {
      setIsOpen(false);
      setIsClosing(false);
      setFocusedIndex(-1);
      setSearchQuery('');
    }, 200); // Match animation duration
  }, []);

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (
        containerRef.current &&
        !containerRef.current.contains(event.target) &&
        menuRef.current &&
        !menuRef.current.contains(event.target)
      ) {
        closeDropdown();
      }
    };

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

  // Memoize items count to avoid unnecessary recalculations
  const itemsCount = useMemo(() =>
    props.items.filter((i) => i !== null).length,
    [props.items]
  );

  const calculatePosition = useCallback(() => {
    if (controlRef.current) {
      const rect = controlRef.current.getBoundingClientRect();
      const gap = 4;
      const viewportHeight = window.innerHeight;

      // Estimate menu height (will be more accurate after first render)
      const estimatedMenuHeight = Math.min(itemsCount * 44, 250);

      // Calculate if dropdown would overflow bottom of viewport
      const spaceBelow = viewportHeight - rect.bottom - gap;
      const spaceAbove = rect.top - gap;

      // If not enough space below but more space above, flip to top
      const shouldFlipUp = spaceBelow < estimatedMenuHeight && spaceAbove > spaceBelow;

      return {
        top: shouldFlipUp ? rect.top - gap : rect.bottom + gap,
        left: rect.left,
        width: rect.width,
        maxHeight: shouldFlipUp ? Math.min(250, spaceAbove) : Math.min(250, spaceBelow),
        flipped: shouldFlipUp,
      };
    }
    return { top: 0, left: 0, width: 0, maxHeight: 250, flipped: false };
  }, [itemsCount]);

  const openDropdown = useCallback(() => {
    const position = calculatePosition();
    setMenuPosition(position);
    setIsOpen(true);
  }, [calculatePosition]);

  // Update dropdown position on scroll or resize
  useEffect(() => {
    if (!isOpen) return;

    let rafId = null;

    const updatePosition = () => {
      if (rafId) window.cancelAnimationFrame(rafId);

      rafId = window.requestAnimationFrame(() => {
        const newPosition = calculatePosition();
        setMenuPosition(newPosition);
      });
    };

    // Listen to window scroll and resize
    window.addEventListener('scroll', updatePosition, { passive: true });
    window.addEventListener('resize', updatePosition, { passive: true });

    // Find and listen to scrollable ancestors
    let element = controlRef.current?.parentElement;
    const scrollableElements = [];
    while (element) {
      const hasScrollableContent = element.scrollHeight > element.clientHeight;
      const overflowYStyle = window.getComputedStyle(element).overflowY;
      const isOverflowYScrollable = overflowYStyle !== 'visible' && overflowYStyle !== 'hidden';

      if (hasScrollableContent && isOverflowYScrollable) {
        scrollableElements.push(element);
        element.addEventListener('scroll', updatePosition, { passive: true });
      }
      element = element.parentElement;
    }

    return () => {
      if (rafId) window.cancelAnimationFrame(rafId);
      window.removeEventListener('scroll', updatePosition);
      window.removeEventListener('resize', updatePosition);
      scrollableElements.forEach(el =>
        el.removeEventListener('scroll', updatePosition)
      );
    };
  }, [isOpen, calculatePosition]);

  useEffect(() => {
    if (isOpen && props.searchable && searchInputRef.current) {
      // Focus the search input when dropdown opens
      setTimeout(() => searchInputRef.current?.focus(), 0);
    }
  }, [isOpen, props.searchable]);

  const handleSearchChange = useCallback((e) => {
    setSearchQuery(e.target.value);
    if (!isOpen) {
      openDropdown();
    }
  }, [isOpen, openDropdown]);

  const handleInputClick = useCallback((e) => {
    e.stopPropagation();
    if (!isOpen) {
      openDropdown();
    }
  }, [isOpen, openDropdown]);

  const handleInputFocus = useCallback(() => {
    // When focusing, if not default value, pre-fill with current value for editing
    const defaultValue = props.default || props.items[0]?.value;
    if (value !== defaultValue && !searchQuery) {
      const currentItem = props.items.find((item) => item?.value === value);
      const currentText = currentItem?.text || value;
      setSearchQuery(currentText);
    }
  }, [value, props.default, props.items, searchQuery]);

  const onChange = useCallback(
    (newValue) => {
      if (newValue === variables.getMessage('modals.main.loading')) {
        return;
      }

      variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);

      setValue(newValue);
      closeDropdown();

      if (!props.noSetting) {
        localStorage.setItem(props.name, newValue);
        localStorage.setItem(props.name2, props.value2);
      }

      if (props.onChange) {
        props.onChange(newValue);
      }

      if (props.element) {
        if (!document.querySelector(props.element)) {
          document.querySelector('.reminder-info').style.display = 'flex';
          return localStorage.setItem('showReminder', true);
        }
      }

      EventBus.emit('refresh', props.category);
    },
    [value, props],
  );

  const handleKeyDown = useCallback(
    (e) => {
      if (props.disabled) return;

      switch (e.key) {
        case 'Enter':
        case ' ':
          e.preventDefault();
          if (isOpen) {
            closeDropdown();
          } else {
            openDropdown();
          }
          break;
        case 'Escape':
          closeDropdown();
          break;
        case 'ArrowDown':
          e.preventDefault();
          if (!isOpen) {
            openDropdown();
          } else {
            setFocusedIndex((prev) =>
              prev < props.items.filter((i) => i !== null).length - 1 ? prev + 1 : prev,
            );
          }
          break;
        case 'ArrowUp':
          e.preventDefault();
          if (isOpen) {
            setFocusedIndex((prev) => (prev > 0 ? prev - 1 : prev));
          }
          break;
      }
    },
    [isOpen, props.items, props.disabled, openDropdown, closeDropdown],
  );

  const handleOptionKeyDown = useCallback(
    (e, item) => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        onChange(item.value);
      }
    },
    [onChange],
  );

  const resetItem = useCallback((e) => {
    e?.stopPropagation();
    const defaultValue = props.default || props.items[0]?.value;
    onChange(defaultValue);
    toast(variables.getMessage('toasts.reset'));
  }, [onChange, props.default, props.items]);

  const clearSearch = useCallback((e) => {
    e.stopPropagation();
    setSearchQuery('');
    if (props.searchable) {
      // Reset to default value (first item, usually "Automatic")
      const defaultValue = props.default || props.items[0]?.value;
      onChange(defaultValue);
    }
    if (searchInputRef.current) {
      searchInputRef.current.focus();
    }
  }, [props, onChange]);

  const id = 'dropdown' + props.name;
  const label = props.label || '';
  const selectedItem = props.items.find((item) => item?.value === value);
  const defaultValue = props.default || props.items[0]?.value;

  // Filter items based on search query
  const filteredItems = props.searchable && searchQuery
    ? props.items.filter((item) =>
        item !== null && item.text.toLowerCase().includes(searchQuery.toLowerCase()),
      )
    : props.items;

  return (
    <div className={`dropdown ${id} ${props.disabled ? 'disabled' : ''}`} ref={containerRef} onClick={(e) => e.stopPropagation()}>
      {label && (
        <div className="dropdown-header">
          <label className="dropdown-label">{label}</label>
          <span className="dropdown-reset" onClick={resetItem}>
            <MdRefresh />
            {variables.getMessage('modals.main.settings.buttons.reset')}
          </span>
        </div>
      )}
      <div
        ref={controlRef}
        className={`dropdown-control ${props.searchable && (isOpen || searchQuery) ? 'searching' : ''}`}
        onClick={(e) => {
          e.stopPropagation();
          if (props.disabled) return;
          if (isOpen) {
            closeDropdown();
          } else {
            openDropdown();
          }
        }}
        onKeyDown={handleKeyDown}
        role="button"
        aria-haspopup="listbox"
        aria-expanded={isOpen}
        aria-label={label || props.name}
        tabIndex={props.disabled ? -1 : 0}
      >
        {props.searchable ? (
          <>
            <input
              ref={searchInputRef}
              type="text"
              className="dropdown-search-input-control"
              placeholder={selectedItem?.text || value}
              value={searchQuery}
              onChange={handleSearchChange}
              onClick={handleInputClick}
              onFocus={handleInputFocus}
              onKeyDown={(e) => {
                if (e.key === 'Escape') {
                  if (searchQuery) {
                    setSearchQuery('');
                  } else {
                    closeDropdown();
                  }
                }
                if (e.key === ' ') {
                  e.stopPropagation();
                }
              }}
            />
            {(searchQuery || value !== (props.default || props.items[0]?.value)) && (
              <MdClose className="dropdown-clear" onClick={clearSearch} />
            )}
          </>
        ) : (
          <span className="dropdown-value">{selectedItem?.text || value}</span>
        )}
        <MdExpandMore className={`dropdown-arrow ${isOpen ? 'open' : ''}`} />
      </div>
      {(isOpen || isClosing) &&
        createPortal(
          <div
            ref={menuRef}
            className={`dropdown-menu ${isClosing ? 'closing' : ''} ${menuPosition.flipped ? 'flipped' : ''}`}
            role="listbox"
            style={{
              position: 'fixed',
              top: `${menuPosition.top}px`,
              left: `${menuPosition.left}px`,
              width: `${menuPosition.width}px`,
              maxHeight: menuPosition.maxHeight ? `${menuPosition.maxHeight}px` : '250px',
              transform: menuPosition.flipped ? 'translateY(-100%)' : 'none',
            }}
          >
            {filteredItems.map((item, index) =>
              item !== null ? (
                <div
                  key={id + item.value}
                  ref={(el) => (optionsRef.current[index] = el)}
                  className={`dropdown-option ${value === item.value ? 'selected' : ''} ${index === focusedIndex ? 'focused' : ''}`}
                  onClick={() => onChange(item.value)}
                  onKeyDown={(e) => handleOptionKeyDown(e, item)}
                  role="option"
                  aria-selected={value === item.value}
                  tabIndex={0}
                >
                  <span className="dropdown-option-text">
                    {item.text}
                    {item.value === defaultValue && (
                      <span className="dropdown-option-default">
                        {' '}
                        ({variables.getMessage('modals.main.settings.buttons.default')})
                      </span>
                    )}
                  </span>
                  {value === item.value && <MdCheck className="dropdown-option-check" />}
                </div>
              ) : null,
            )}
          </div>,
          document.body,
        )}
    </div>
  );
});

Dropdown.displayName = 'Dropdown';

export { Dropdown as default, Dropdown };
