import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { Check, ChevronDown, Search } from 'lucide-react'
import styles from './MultiSelectDropdown.module.css'

export interface MultiSelectOption<T extends string | number = number> {
  value: T
  label: string
  depth?: number
}

interface MultiSelectDropdownProps<T extends string | number = number> {
  options: MultiSelectOption<T>[]
  value: T[]
  onChange: (value: T[]) => void
  placeholder?: string
  searchPlaceholder?: string
  searchable?: boolean
  disabled?: boolean
  id?: string
}

export function MultiSelectDropdown<T extends string | number = number>({
  options,
  value,
  onChange,
  placeholder = 'Select options',
  searchPlaceholder = 'Search...',
  searchable = false,
  disabled = false,
  id,
}: MultiSelectDropdownProps<T>) {
  const [open, setOpen] = useState(false)
  const [searchQuery, setSearchQuery] = useState('')
  const [triggerLabel, setTriggerLabel] = useState(placeholder)
  const containerRef = useRef<HTMLDivElement>(null)
  const triggerRef = useRef<HTMLButtonElement>(null)
  const measureRef = useRef<HTMLSpanElement>(null)
  const searchInputRef = useRef<HTMLInputElement>(null)

  const selectedLabels = useMemo(
    () =>
      options
        .filter((option) => value.includes(option.value))
        .map((option) => option.label),
    [options, value],
  )

  useLayoutEffect(() => {
    const trigger = triggerRef.current
    const measure = measureRef.current

    if (!trigger || !measure) {
      setTriggerLabel(selectedLabels.length === 0 ? placeholder : selectedLabels.join(', '))
      return
    }

    const triggerEl = trigger
    const measureEl = measure

    function buildLabel(visibleCount: number) {
      const hiddenCount = selectedLabels.length - visibleCount
      if (hiddenCount <= 0) {
        return selectedLabels.join(', ')
      }
      return `${selectedLabels.slice(0, visibleCount).join(', ')} +${hiddenCount}`
    }

    function fitLabel() {
      if (selectedLabels.length === 0) {
        setTriggerLabel(placeholder)
        return
      }

      const style = getComputedStyle(triggerEl)
      measureEl.style.font = style.font

      const padding =
        parseFloat(style.paddingLeft) +
        parseFloat(style.paddingRight)
      const gap = parseFloat(style.columnGap || style.gap || '10')
      const reservedWidth = padding + gap + 16 + (searchable ? gap + 16 : 0)
      const availableWidth = triggerEl.clientWidth - reservedWidth

      for (let visibleCount = selectedLabels.length; visibleCount >= 1; visibleCount -= 1) {
        const label = buildLabel(visibleCount)
        measureEl.textContent = label
        if (measureEl.offsetWidth <= availableWidth) {
          setTriggerLabel(label)
          return
        }
      }

      setTriggerLabel(buildLabel(1))
    }

    fitLabel()

    const observer = new ResizeObserver(fitLabel)
    observer.observe(triggerEl)
    return () => observer.disconnect()
  }, [placeholder, searchable, selectedLabels])

  const filteredOptions = useMemo(() => {
    const query = searchQuery.trim().toLowerCase()
    if (!searchable || !query) return options
    return options.filter((option) => option.label.toLowerCase().includes(query))
  }, [options, searchQuery, searchable])

  useEffect(() => {
    if (!open) {
      setSearchQuery('')
      return
    }

    function handleClickOutside(e: MouseEvent) {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
        setOpen(false)
      }
    }

    function handleEscape(e: KeyboardEvent) {
      if (e.key === 'Escape') setOpen(false)
    }

    document.addEventListener('mousedown', handleClickOutside)
    document.addEventListener('keydown', handleEscape)
    return () => {
      document.removeEventListener('mousedown', handleClickOutside)
      document.removeEventListener('keydown', handleEscape)
    }
  }, [open])

  useEffect(() => {
    if (open && searchable) {
      searchInputRef.current?.focus()
    }
  }, [open, searchable])

  function toggleOption(optionValue: T) {
    if (value.includes(optionValue)) {
      onChange(value.filter((item) => item !== optionValue))
      return
    }
    onChange([...value, optionValue])
  }

  return (
    <div className={styles.wrapper} ref={containerRef}>
      <span ref={measureRef} className={styles.measureText} aria-hidden="true" />
      <button
        ref={triggerRef}
        id={id}
        type="button"
        className={`${styles.trigger} ${open ? styles.triggerOpen : ''} ${
          selectedLabels.length === 0 ? styles.triggerPlaceholder : ''
        }`}
        onClick={() => !disabled && setOpen((prev) => !prev)}
        disabled={disabled}
        aria-haspopup="listbox"
        aria-expanded={open}
      >
        {searchable && <Search size={16} className={styles.triggerSearchIcon} aria-hidden="true" />}
        <span className={styles.triggerText}>{triggerLabel}</span>
        <ChevronDown size={16} className={`${styles.chevron} ${open ? styles.chevronOpen : ''}`} />
      </button>

      {open && (
        <div className={styles.dropdown}>
          {searchable && (
            <div className={styles.searchWrap}>
              <Search size={16} className={styles.searchIcon} aria-hidden="true" />
              <input
                ref={searchInputRef}
                type="search"
                className={styles.searchInput}
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                placeholder={searchPlaceholder}
                onKeyDown={(e) => e.stopPropagation()}
              />
            </div>
          )}

          <ul className={styles.optionsList} role="listbox" aria-multiselectable="true">
            {filteredOptions.length === 0 ? (
              <li className={styles.empty}>
                {options.length === 0 ? 'No options available.' : 'No matching options.'}
              </li>
            ) : (
              filteredOptions.map((option) => {
                const selected = value.includes(option.value)
                return (
                  <li key={String(option.value)}>
                    <button
                      type="button"
                      role="option"
                      aria-selected={selected}
                      className={`${styles.option} ${selected ? styles.optionSelected : ''}`}
                      style={{ paddingLeft: `${10 + (option.depth ?? 0) * 18}px` }}
                      onClick={() => toggleOption(option.value)}
                    >
                      <span className={`${styles.check} ${selected ? styles.checkSelected : ''}`}>
                        {selected && <Check size={12} strokeWidth={3} />}
                      </span>
                      <span>{option.label}</span>
                    </button>
                  </li>
                )
              })
            )}
          </ul>
        </div>
      )}
    </div>
  )
}
