import * as React from 'react'
import { CheckIcon, CaretDownIcon, MagnifyingGlassIcon } from '@phosphor-icons/react'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from 'cmdk'
import { Popover, PopoverTrigger, PopoverContent } from './popover'
import { FieldLabel, FieldHint, FieldError } from './field'
import { Badge } from './badge'
import { cn } from '@/lib/utils'

/**
 * MultiSelect — popover с inline checkboxes. Замена @nstart/ui MultiSelect.
 *
 * <p>Trigger показывает count selected + первые 2 selected labels.
 * Popover — список с checkboxes. Click toggle.
 *
 * <p><b>Searchable mode</b> (opt-in): {@code searchable=true} включает
 * cmdk Combobox c keyword filter + keyboard navigation (↑/↓/Enter/Esc).
 * Имеет смысл когда options 10+ или labels длинные. Cmdk фильтрует на
 * клиенте по substring match label'а — для server-side search будущий
 * follow-up (async loader prop).
 *
 * <p>API: options [{id, label}], value string[], onChange (ids: string[]).
 */

/** nstart-compat: {id, label}. Use `id` key. */
export type MultiSelectOption = {
  id: string
  label: React.ReactNode
  /** Alias of `id`. */
  value?: string
}

export type MultiSelectProps = {
  label?: React.ReactNode
  hint?: React.ReactNode
  error?: React.ReactNode
  required?: boolean
  options: MultiSelectOption[]
  value: string[]
  onChange: (next: string[]) => void
  placeholder?: string
  disabled?: boolean
  /** Wrapper className. */
  rootClassName?: string
  /** Compact display (no badges, just count). */
  compact?: boolean
  /** Включает cmdk-powered поиск (input + keyboard nav). Default false. */
  searchable?: boolean
  /** Placeholder for search input (только если searchable). */
  searchPlaceholder?: string
  /** Message when filter yields zero results (только если searchable). */
  emptyMessage?: React.ReactNode
}

export function MultiSelect({
  label,
  hint,
  error,
  required,
  options,
  value,
  onChange,
  placeholder,
  disabled,
  rootClassName,
  compact,
  searchable,
  searchPlaceholder,
  emptyMessage,
}: MultiSelectProps) {
  const autoId = React.useId()
  const triggerId = autoId
  const hintId = hint ? `${triggerId}-hint` : undefined
  const errorId = error ? `${triggerId}-error` : undefined

  const valueSet = React.useMemo(() => new Set(value), [value])
  const toggle = (id: string) => {
    const next = new Set(valueSet)
    if (next.has(id)) next.delete(id)
    else next.add(id)
    onChange(Array.from(next))
  }

  const selectedLabels = options
    .filter((o) => valueSet.has(o.id ?? o.value ?? ''))
    .map((o) => o.label)

  return (
    <div className={cn('flex flex-col gap-1', rootClassName)}>
      {label && (
        <FieldLabel htmlFor={triggerId} required={required}>
          {label}
        </FieldLabel>
      )}
      <Popover>
        <PopoverTrigger asChild>
          <button
            type="button"
            id={triggerId}
            disabled={disabled}
            aria-invalid={error ? true : undefined}
            aria-describedby={[hintId, errorId].filter(Boolean).join(' ') || undefined}
            className={cn(
              'flex items-center justify-between gap-2 h-9 w-full rounded-md border border-line bg-surface px-3 text-body text-ink',
              'hover:border-line-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
              'disabled:cursor-not-allowed disabled:opacity-50',
              error && 'border-pink focus-visible:ring-pink/40',
            )}
          >
            <span className="flex-1 min-w-0 flex items-center gap-1.5 overflow-hidden">
              {selectedLabels.length === 0 ? (
                <span className="text-mute truncate">{placeholder ?? '—'}</span>
              ) : compact ? (
                <span className="text-mono">
                  {selectedLabels.length} / {options.length}
                </span>
              ) : selectedLabels.length <= 2 ? (
                selectedLabels.map((lbl, i) => (
                  <Badge key={i} variant="info">
                    {lbl}
                  </Badge>
                ))
              ) : (
                <>
                  <Badge variant="info">{selectedLabels[0]}</Badge>
                  <Badge variant="info">+{selectedLabels.length - 1}</Badge>
                </>
              )}
            </span>
            <CaretDownIcon size={14} weight="bold" className="shrink-0 text-mute" />
          </button>
        </PopoverTrigger>
        <PopoverContent align="start" className="p-1 min-w-[var(--radix-popover-trigger-width)]">
          <PopoverPrimitive.Close className="hidden" />
          {searchable ? (
            <Command
              // cmdk использует label option'а как searchable value через
              // `value={String(label)}` — но options могут иметь React.ReactNode
              // label'ы (мы поддерживаем JSX в label, см. webhooks). Чтобы
              // фильтр работал, передаём value явно — toString() от label если
              // оно strings, иначе id. Custom filter handle ниже.
              filter={(itemValue, search) => {
                if (!search) return 1
                return itemValue.toLowerCase().includes(search.toLowerCase()) ? 1 : 0
              }}
              className="flex flex-col"
            >
              <div className="flex items-center gap-2 px-2 border-b border-line">
                <MagnifyingGlassIcon size={14} weight="regular" className="shrink-0 text-mute" />
                <CommandInput
                  placeholder={searchPlaceholder ?? 'Поиск…'}
                  className="flex-1 h-8 bg-transparent border-0 outline-none text-body text-ink placeholder:text-mute"
                />
              </div>
              <CommandList className="max-h-64 overflow-y-auto p-1">
                <CommandEmpty className="px-2.5 py-4 text-body text-mute text-center">
                  {emptyMessage ?? 'Ничего не найдено'}
                </CommandEmpty>
                <CommandGroup>
                  {options.map((opt) => {
                    const optId = opt.id ?? opt.value ?? ''
                    const checked = valueSet.has(optId)
                    // cmdk использует value для filter + keyboard match.
                    // Передаём labels-as-string + id чтобы оба варианта поиска
                    // работали ("admin" найдёт ADMIN, и "ADMIN" тоже).
                    const searchValue = `${typeof opt.label === 'string' ? opt.label : ''} ${optId}`.trim()
                    return (
                      <CommandItem
                        key={optId}
                        value={searchValue}
                        onSelect={() => toggle(optId)}
                        className={cn(
                          'flex items-center gap-2 px-2.5 py-1.5 rounded-sm cursor-pointer text-body',
                          'aria-selected:bg-surface-2 data-[selected=true]:bg-surface-2',
                          'hover:bg-surface-2 focus:bg-surface-2 outline-none',
                          checked && 'text-ink',
                        )}
                      >
                        <span
                          className={cn(
                            'inline-flex items-center justify-center size-4 rounded-sm border',
                            checked
                              ? 'bg-accent border-accent text-on-accent'
                              : 'border-line-2 bg-surface',
                          )}
                          aria-hidden="true"
                        >
                          {checked && <CheckIcon size={10} weight="bold" />}
                        </span>
                        <span className="flex-1 truncate">{opt.label}</span>
                      </CommandItem>
                    )
                  })}
                </CommandGroup>
              </CommandList>
            </Command>
          ) : (
            <ul role="listbox" aria-multiselectable className="max-h-64 overflow-y-auto">
              {options.map((opt) => {
                const checked = valueSet.has((opt.id ?? opt.value ?? ''))
                return (
                  <li
                    key={(opt.id ?? opt.value ?? '')}
                    role="option"
                    aria-selected={checked}
                    tabIndex={0}
                    onClick={() => toggle((opt.id ?? opt.value ?? ''))}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault()
                        toggle((opt.id ?? opt.value ?? ''))
                      }
                    }}
                    className={cn(
                      'flex items-center gap-2 px-2.5 py-1.5 rounded-sm cursor-pointer text-body',
                      'hover:bg-surface-2 focus:bg-surface-2 focus:outline-none',
                      checked && 'text-ink',
                    )}
                  >
                    <span
                      className={cn(
                        'inline-flex items-center justify-center size-4 rounded-sm border',
                        checked
                          ? 'bg-accent border-accent text-on-accent'
                          : 'border-line-2 bg-surface',
                      )}
                      aria-hidden="true"
                    >
                      {checked && <CheckIcon size={10} weight="bold" />}
                    </span>
                    <span className="flex-1 truncate">{opt.label}</span>
                  </li>
                )
              })}
            </ul>
          )}
        </PopoverContent>
      </Popover>
      {hint && !error && <FieldHint id={hintId}>{hint}</FieldHint>}
      {error && <FieldError id={errorId}>{error}</FieldError>}
    </div>
  )
}
