import React, {
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react'
import { createPortal } from 'react-dom'

/**
 * Nova NovaSelect – Select2-style dropdown
 *
 * Options format:  [{ value, label, icon?, disabled?, group? }]
 *
 * @prop {Array}    options       - list of option objects
 * @prop {*}        value         - selected value (or array of values in multi mode)
 * @prop {function} onChange      - called with new value (or array in multi mode)
 * @prop {boolean}  multi         - allow multiple selections
 * @prop {string}   placeholder   - placeholder text
 * @prop {boolean}  searchable    - filter options by typing (default true)
 * @prop {boolean}  clearable     - show clear button when a value is selected
 * @prop {string}   label         - label above the trigger
 * @prop {string}   error         - validation error
 * @prop {string}   hint          - helper text
 * @prop {boolean}  required      - asterisk on label
 * @prop {boolean}  disabled
 * @prop {function} renderOption  - custom render fn: (option) => ReactNode
 */
export default function NovaSelect({
  options = [],
  value,
  onChange,
  multi = false,
  placeholder = 'Select…',
  searchable = true,
  clearable = false,
  label,
  error,
  hint,
  required = false,
  disabled = false,
  renderOption,
  id,
  className = '',
}) {
  const [open, setOpen]         = useState(false)
  const [search, setSearch]     = useState('')
  const [highlighted, setHigh]  = useState(-1)
  const [dropPos, setDropPos]   = useState({ top: 0, left: 0, width: 300, openUp: false })

  const triggerRef  = useRef(null)
  const searchRef   = useRef(null)
  const listRef     = useRef(null)
  const inputId     = id ?? (label ? `nova-select-${label.toLowerCase().replace(/\s+/g, '-')}` : 'nova-select')

  // Normalize value to array internally
  const selected = useMemo(() => {
    if (multi) return Array.isArray(value) ? value : (value != null ? [value] : [])
    return value != null ? [value] : []
  }, [value, multi])

  const selectedSet = useMemo(() => new Set(selected.map(String)), [selected])

  // Filtered + grouped options
  const filtered = useMemo(() => {
    const q = search.toLowerCase()
    return options.filter((o) => !q || o.label.toLowerCase().includes(q))
  }, [options, search])

  // Compute dropdown position from trigger bounding rect
  const measurePosition = useCallback(() => {
    if (!triggerRef.current) return
    const rect  = triggerRef.current.getBoundingClientRect()
    const spaceBelow = window.innerHeight - rect.bottom
    const spaceAbove = rect.top
    const dropH = Math.min(280, filtered.length * 38 + 52) // approx
    const openUp = spaceBelow < dropH + 8 && spaceAbove > spaceBelow

    // Clamp horizontal position so the dropdown doesn't render off-screen or far away
    const padding = 8
    const leftClamped = Math.max(padding, Math.min(rect.left, window.innerWidth - rect.width - padding))

    setDropPos({
      top:   openUp ? rect.top - dropH - 4 : rect.bottom + 4,
      left:  leftClamped,
      width: rect.width,
      openUp,
    })
  }, [filtered.length])

  const openDropdown = useCallback(() => {
    if (disabled) return
    measurePosition()
    setOpen(true)
    setHigh(-1)
  }, [disabled, measurePosition])

  const closeDropdown = useCallback(() => {
    setOpen(false)
    setSearch('')
    setHigh(-1)
  }, [])

  // Focus search when opened
  useLayoutEffect(() => {
    if (open && searchable) {
      setTimeout(() => searchRef.current?.focus(), 0)
    }
  }, [open, searchable])

  // Close dropdown when scrolling outside it (prevents portal drifting from trigger)
  useEffect(() => {
    if (!open) return
    const onScroll = (e) => {
      const dropdown = document.getElementById(`nova-select-dropdown-${inputId}`)
      if (dropdown && dropdown.contains(e.target)) return // scrolling inside list — keep open
      closeDropdown()
    }
    const onResize = () => closeDropdown()
    window.addEventListener('scroll', onScroll, true)
    window.addEventListener('resize', onResize)
    return () => {
      window.removeEventListener('scroll', onScroll, true)
      window.removeEventListener('resize', onResize)
    }
  }, [open, closeDropdown, inputId])

  // Click outside
  useEffect(() => {
    if (!open) return
    const handler = (e) => {
      if (
        !triggerRef.current?.contains(e.target) &&
        !document.getElementById(`nova-select-dropdown-${inputId}`)?.contains(e.target)
      ) {
        closeDropdown()
      }
    }
    document.addEventListener('mousedown', handler)
    return () => document.removeEventListener('mousedown', handler)
  }, [open, closeDropdown, inputId])

  // Scroll highlighted item into view
  useEffect(() => {
    if (highlighted < 0 || !listRef.current) return
    const item = listRef.current.querySelectorAll('[data-option]')[highlighted]
    item?.scrollIntoView({ block: 'nearest' })
  }, [highlighted])

  const selectOption = useCallback((opt) => {
    if (opt.disabled) return
    if (multi) {
      const exists = selectedSet.has(String(opt.value))
      onChange(exists ? selected.filter((v) => String(v) !== String(opt.value)) : [...selected, opt.value])
      setSearch('')
      searchRef.current?.focus()
    } else {
      onChange(opt.value)
      closeDropdown()
      triggerRef.current?.focus()
    }
  }, [multi, selected, selectedSet, onChange, closeDropdown])

  const clearValue = useCallback((e) => {
    e.stopPropagation()
    onChange(multi ? [] : null)
  }, [multi, onChange])

  const removeTag = useCallback((val, e) => {
    e.stopPropagation()
    onChange(selected.filter((v) => String(v) !== String(val)))
  }, [selected, onChange])

  // Keyboard handler on search/trigger
  const handleKeyDown = useCallback((e) => {
    if (!open) {
      if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
        e.preventDefault()
        openDropdown()
      }
      return
    }

    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault()
        setHigh((h) => Math.min(h + 1, filtered.length - 1))
        break
      case 'ArrowUp':
        e.preventDefault()
        setHigh((h) => Math.max(h - 1, 0))
        break
      case 'Enter':
        e.preventDefault()
        if (highlighted >= 0 && filtered[highlighted]) selectOption(filtered[highlighted])
        break
      case 'Escape':
        e.preventDefault()
        closeDropdown()
        triggerRef.current?.focus()
        break
      case 'Backspace':
        if (multi && !search && selected.length > 0) {
          onChange(selected.slice(0, -1))
        }
        break
      default:
        break
    }
  }, [open, filtered, highlighted, search, multi, selected, selectOption, closeDropdown, openDropdown, onChange])

  // Build display label(s)
  const labelMap = useMemo(() => Object.fromEntries(options.map((o) => [String(o.value), o.label])), [options])

  const hasValue = selected.length > 0

  // Trigger appearance
  const triggerClass = [
    'relative w-full flex items-center min-h-[42px] rounded-xl border px-3 py-1.5 gap-2 cursor-pointer',
    'bg-white/[0.06] text-sm text-white transition-all duration-150',
    'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-0',
    error
      ? 'border-red-500/60 focus-visible:ring-red-500/40'
      : open
        ? 'border-accent/50 ring-2 ring-accent/40'
        : 'border-white/12 hover:border-white/22',
    disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : '',
    className,
  ].join(' ')

  return (
    <div className="flex flex-col gap-1.5">
      {label && (
        <label htmlFor={inputId} className="text-sm font-medium text-white/85 select-none">
          {label}{required && <span className="text-red-400 ml-1">*</span>}
        </label>
      )}

      {/* Trigger button */}
      <div
        ref={triggerRef}
        id={inputId}
        role="combobox"
        aria-expanded={open}
        aria-haspopup="listbox"
        aria-label={label ?? placeholder}
        tabIndex={disabled ? -1 : 0}
        className={triggerClass}
        onClick={open ? closeDropdown : openDropdown}
        onKeyDown={handleKeyDown}
      >
        {/* Tags (multi) or selected label (single) */}
        <div className="flex flex-wrap gap-1 flex-1 min-w-0">
          {multi && selected.map((v) => (
            <span
              key={v}
              className="inline-flex items-center gap-1 h-6 px-2 rounded-md bg-accent/20 text-accent text-xs font-medium"
            >
              {labelMap[String(v)] ?? v}
              <button
                type="button"
                tabIndex={-1}
                onClick={(e) => removeTag(v, e)}
                className="hover:text-white transition-colors"
                aria-label={`Remove ${labelMap[String(v)] ?? v}`}
              >
                <svg width="8" height="8" viewBox="0 0 8 8" fill="none" aria-hidden="true">
                  <path d="M1 1l6 6M7 1L1 7" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
                </svg>
              </button>
            </span>
          ))}

          {!multi && hasValue && (
            <span className="truncate text-white">{labelMap[String(selected[0])] ?? selected[0]}</span>
          )}

          {!hasValue && (
            <span className="text-slate-500 truncate">{placeholder}</span>
          )}
        </div>

        {/* Right icons */}
        <div className="flex items-center gap-1 shrink-0">
          {clearable && hasValue && (
            <button
              type="button"
              tabIndex={-1}
              onClick={clearValue}
              className="w-5 h-5 flex items-center justify-center rounded text-slate-500 hover:text-white transition-colors"
              aria-label="Clear"
            >
              <svg width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true">
                <path d="M1 1l8 8M9 1L1 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
              </svg>
            </button>
          )}
          <svg
            width="12"
            height="12"
            viewBox="0 0 12 12"
            fill="none"
            aria-hidden="true"
            className={`text-slate-500 transition-transform duration-150 ${open ? 'rotate-180' : ''}`}
          >
            <path d="M2 4l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </div>
      </div>

      {error && <p role="alert" className="text-xs text-red-400">{error}</p>}
      {!error && hint && <p className="text-xs text-slate-500">{hint}</p>}

      {/* Dropdown portal */}
      {open && createPortal(
        <div
          id={`nova-select-dropdown-${inputId}`}
          role="listbox"
          aria-multiselectable={multi}
          className="fixed z-[500] flex flex-col rounded-xl border border-white/12 bg-nova-900/80 backdrop-blur-xl shadow-2xl shadow-black/50 overflow-hidden"
          style={{ top: dropPos.top, left: dropPos.left, width: dropPos.width, maxHeight: 280 }}
        >
          {/* Search */}
          {searchable && (
            <div className="px-2 pt-2 pb-1 border-b border-white/8">
              <div className="relative">
                <svg
                  width="12" height="12" viewBox="0 0 12 12" fill="none"
                  className="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-500 pointer-events-none"
                  aria-hidden="true"
                >
                  <circle cx="5" cy="5" r="3.5" stroke="currentColor" strokeWidth="1.5" />
                  <path d="M8 8l2.5 2.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
                </svg>
                <input
                  ref={searchRef}
                  type="text"
                  value={search}
                  onChange={(e) => { setSearch(e.target.value); setHigh(0) }}
                  onKeyDown={handleKeyDown}
                  placeholder="Search…"
                  className="w-full pl-3 pr-7 py-1.5 rounded-lg bg-white/5 border border-white/8 text-white text-xs placeholder:text-slate-500 focus:outline-none focus:ring-1 focus:ring-accent/50"
                  autoComplete="off"
                />
              </div>
            </div>
          )}

          {/* Options */}
          <div ref={listRef} className="overflow-y-auto flex-1 nova-scrollbar" style={{ maxHeight: 220 }}>
            {filtered.length === 0 ? (
              <p className="px-3 py-6 text-center text-xs text-slate-500">No options found</p>
            ) : (
              filtered.map((opt, idx) => {
                const isSelected = selectedSet.has(String(opt.value))
                const isHighlighted = idx === highlighted
                const prevGroup = idx > 0 ? filtered[idx - 1].group : undefined
                const showGroupHeader = opt.group != null && opt.group !== prevGroup

                return (
                  <React.Fragment key={String(opt.value)}>
                    {showGroupHeader && (
                      <div className={`px-3 pt-2 pb-0.5 text-[10px] font-semibold text-slate-500 uppercase tracking-widest select-none${idx > 0 ? ' border-t border-white/5' : ''}`}>
                        {opt.group}
                      </div>
                    )}
                    <div
                      data-option
                    role="option"
                    aria-selected={isSelected}
                    aria-disabled={opt.disabled}
                    onClick={() => selectOption(opt)}
                    onMouseEnter={() => setHigh(idx)}
                    className={[
                      'flex items-center gap-2.5 px-3 py-2 text-sm cursor-pointer transition-colors duration-75',
                      opt.disabled ? 'opacity-40 cursor-not-allowed' : '',
                      isHighlighted ? 'bg-white/[0.13]' : 'hover:bg-white/[0.07]',
                      isSelected ? 'text-accent' : 'text-white/85',
                    ].join(' ')}
                  >
                    {/* Checkmark in multi mode */}
                    {multi && (
                      <span className={[
                        'w-4 h-4 shrink-0 rounded border flex items-center justify-center transition-colors',
                        isSelected ? 'bg-accent border-accent' : 'border-white/25 bg-white/5',
                      ].join(' ')}>
                        {isSelected && (
                          <svg width="9" height="9" viewBox="0 0 9 9" fill="none" aria-hidden="true">
                            <path d="M1.5 4.5l2 2 4-4" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
                          </svg>
                        )}
                      </span>
                    )}

                    {opt.icon && <span className="shrink-0">{opt.icon}</span>}

                    {renderOption ? renderOption(opt) : (
                      <span className="flex-1 truncate">{opt.label}</span>
                    )}

                    {/* Tick in single mode */}
                    {!multi && isSelected && (
                      <svg width="12" height="12" viewBox="0 0 12 12" fill="none" className="text-accent shrink-0" aria-hidden="true">
                        <path d="M2 6l3 3 5-5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
                      </svg>
                    )}
                    </div>
                  </React.Fragment>
                )
              })
            )}
          </div>
        </div>,
        document.body,
      )}
    </div>
  )
}
