import {
  Children,
  isValidElement,
  useEffect,
  useId,
  useMemo,
  useRef,
  useState,
  type ButtonHTMLAttributes,
  type ReactNode,
} from 'react'
import { Check, ChevronDown } from 'lucide-react'
import { createPortal } from 'react-dom'
import { cn } from '@/lib/utils'

export type SelectOption = {
  value: string
  label: ReactNode
  disabled?: boolean
}

type ChangeHandler = (event: { target: { value: string } }) => void

export interface SelectProps {
  value?: string
  defaultValue?: string
  onChange?: ChangeHandler
  onValueChange?: (value: string) => void
  options?: SelectOption[]
  children?: ReactNode
  placeholder?: string
  className?: string
  disabled?: boolean
  name?: string
  id?: string
}

function parseOptionsFromChildren(children: ReactNode): SelectOption[] {
  const list: SelectOption[] = []
  Children.forEach(children, (child) => {
    if (!isValidElement(child)) return
    if (child.type !== 'option') return
    const props = child.props as {
      value?: string | number
      disabled?: boolean
      children?: ReactNode
    }
    list.push({
      value: props.value == null ? '' : String(props.value),
      label: props.children,
      disabled: Boolean(props.disabled),
    })
  })
  return list
}

export function Select({
  value,
  defaultValue = '',
  onChange,
  onValueChange,
  options,
  children,
  placeholder = '请选择',
  className,
  disabled,
  name,
  id,
}: SelectProps) {
  const reactId = useId()
  const listboxId = id || `select-${reactId}`
  const rootRef = useRef<HTMLDivElement>(null)
  const triggerRef = useRef<HTMLButtonElement>(null)
  const menuRef = useRef<HTMLDivElement>(null)
  const [open, setOpen] = useState(false)
  const [uncontrolled, setUncontrolled] = useState(defaultValue)
  const [menuStyle, setMenuStyle] = useState<React.CSSProperties>({})

  const parsed = useMemo(() => {
    if (options?.length) return options
    return parseOptionsFromChildren(children)
  }, [options, children])

  const current = value !== undefined ? value : uncontrolled
  const selected = parsed.find((o) => o.value === current)
  const display = selected?.label ?? (current ? current : null)

  function commit(next: string) {
    if (value === undefined) setUncontrolled(next)
    onValueChange?.(next)
    onChange?.({ target: { value: next } })
    setOpen(false)
  }

  function updateMenuPosition() {
    const el = triggerRef.current
    if (!el) return
    const rect = el.getBoundingClientRect()
    const viewportH = window.innerHeight
    const spaceBelow = viewportH - rect.bottom
    const openUp = spaceBelow < 220 && rect.top > spaceBelow
    setMenuStyle({
      position: 'fixed',
      left: rect.left,
      width: Math.max(rect.width, 140),
      zIndex: 80,
      ...(openUp
        ? { bottom: viewportH - rect.top + 6, top: 'auto' }
        : { top: rect.bottom + 6, bottom: 'auto' }),
    })
  }

  useEffect(() => {
    if (!open) return
    updateMenuPosition()
    const onDoc = (e: MouseEvent) => {
      const t = e.target as Node
      if (rootRef.current?.contains(t) || menuRef.current?.contains(t)) return
      setOpen(false)
    }
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setOpen(false)
    }
    const onReposition = () => updateMenuPosition()
    document.addEventListener('mousedown', onDoc)
    document.addEventListener('keydown', onKey)
    window.addEventListener('resize', onReposition)
    window.addEventListener('scroll', onReposition, true)
    return () => {
      document.removeEventListener('mousedown', onDoc)
      document.removeEventListener('keydown', onKey)
      window.removeEventListener('resize', onReposition)
      window.removeEventListener('scroll', onReposition, true)
    }
  }, [open])

  return (
    <div ref={rootRef} className={cn('relative w-full', className)}>
      {name ? <input type="hidden" name={name} value={current} /> : null}
      <button
        ref={triggerRef}
        id={listboxId}
        type="button"
        disabled={disabled}
        aria-haspopup="listbox"
        aria-expanded={open}
        className={cn('ui-control ui-select-trigger h-9 px-3 text-left', open && 'ui-control-open')}
        onClick={() => {
          if (disabled) return
          setOpen((v) => !v)
        }}
      >
        <span className={cn('min-w-0 flex-1 truncate', !display && 'text-[var(--text-muted)]')}>
          {display ?? placeholder}
        </span>
        <ChevronDown
          size={14}
          strokeWidth={1.5}
          className={cn('shrink-0 text-[var(--text-muted)] transition-transform', open && 'rotate-180')}
        />
      </button>

      {open &&
        createPortal(
          <div
            ref={menuRef}
            role="listbox"
            aria-labelledby={listboxId}
            className="ui-select-menu"
            style={menuStyle}
          >
            {parsed.length === 0 ? (
              <div className="px-3 py-2 text-sm text-[var(--text-muted)]">暂无选项</div>
            ) : (
              parsed.map((opt) => {
                const active = opt.value === current
                return (
                  <button
                    key={`${opt.value}__${String(opt.label)}`}
                    type="button"
                    role="option"
                    aria-selected={active}
                    disabled={opt.disabled}
                    className={cn('ui-select-item', active && 'is-active')}
                    onClick={() => {
                      if (opt.disabled) return
                      commit(opt.value)
                    }}
                  >
                    <span className="min-w-0 flex-1 truncate">{opt.label}</span>
                    {active ? <Check size={14} strokeWidth={1.75} className="shrink-0 opacity-90" /> : null}
                  </button>
                )
              })
            )}
          </div>,
          document.body,
        )}
    </div>
  )
}

/** 兼容旧的原生 option 写法，无需改调用方 */
export type NativeSelectProps = ButtonHTMLAttributes<HTMLButtonElement>
