import { useMemo, useState } from 'react'
import { Search } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Checkbox } from '@/components/ui/checkbox'
import { Input } from '@/components/ui/input'
import { ScrollArea } from '@/components/ui/scroll-area'

type SelectableId = string | number

type SelectableListProps<TItem> = {
  items: TItem[]
  selectedIds: SelectableId[]
  onToggle: (id: SelectableId) => void
  getId: (item: TItem) => SelectableId
  getTitle: (item: TItem) => React.ReactNode
  getSubtitle?: (item: TItem) => React.ReactNode
  getSearchText?: (item: TItem) => string
  searchPlaceholder?: string
  emptyText?: string
  className?: string
  maxHeightClassName?: string
}

function stringifyNode(value: React.ReactNode) {
  if (typeof value === 'string' || typeof value === 'number') {
    return String(value)
  }
  return ''
}

export function SelectableList<TItem>({
  items,
  selectedIds,
  onToggle,
  getId,
  getTitle,
  getSubtitle,
  getSearchText,
  searchPlaceholder = 'Search...',
  emptyText = 'No options available',
  className,
  maxHeightClassName = 'h-[360px]',
}: SelectableListProps<TItem>) {
  const [keyword, setKeyword] = useState('')
  const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds])

  const filteredItems = useMemo(() => {
    const normalizedKeyword = keyword.trim().toLowerCase()
    if (!normalizedKeyword) return items

    return items.filter((item) => {
      const text =
        getSearchText?.(item) ??
        [getTitle(item), getSubtitle?.(item)]
          .map((value) => stringifyNode(value))
          .filter(Boolean)
          .join(' ')
      return text.toLowerCase().includes(normalizedKeyword)
    })
  }, [getSearchText, getSubtitle, getTitle, items, keyword])

  return (
    <div className={cn('space-y-3', className)}>
      <div className='flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between'>
        <div className='relative sm:max-w-xs sm:flex-1'>
          <Search className='absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground' />
          <Input
            value={keyword}
            onChange={(event) => setKeyword(event.target.value)}
            placeholder={searchPlaceholder}
            className='pl-9'
          />
        </div>
        <div className='text-sm text-muted-foreground'>
          Selected {selectedIds.length} / {items.length}
        </div>
      </div>

      <ScrollArea
        className={cn('rounded-lg border bg-muted/20', maxHeightClassName)}
      >
        <div className='space-y-2 p-2'>
          {filteredItems.length === 0 ? (
            <div className='flex h-28 items-center justify-center text-sm text-muted-foreground'>
              {emptyText}
            </div>
          ) : (
            filteredItems.map((item) => {
              const id = getId(item)
              const checked = selectedSet.has(id)
              return (
                <label
                  key={String(id)}
                  className={cn(
                    'flex cursor-pointer items-start gap-3 rounded-md border bg-background p-3 text-sm transition-colors hover:bg-accent/60',
                    checked && 'border-primary/35 bg-primary/5'
                  )}
                >
                  <Checkbox
                    checked={checked}
                    onCheckedChange={() => onToggle(id)}
                    className='mt-0.5'
                  />
                  <div className='min-w-0 flex-1'>
                    <div className='leading-5 font-medium break-words'>
                      {getTitle(item)}
                    </div>
                    {getSubtitle && (
                      <div className='mt-1 text-xs leading-5 break-words text-muted-foreground'>
                        {getSubtitle(item)}
                      </div>
                    )}
                  </div>
                </label>
              )
            })
          )}
        </div>
      </ScrollArea>
    </div>
  )
}
