'use client'

import * as React from 'react'
import { CheckIcon, ChevronDownIcon, Loader2Icon } from 'lucide-react'
import { cn } from '../lib/utils'
import { Button } from './ui/button'
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from './ui/command'
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from './ui/popover'

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

type BaseComboboxDropdownProps = {
  options: ComboboxOption[]
  placeholder?: string
  clearable?: boolean
  searchable?: boolean
  searchPlaceholder?: string
  emptyText?: string
  disabled?: boolean
  isPending?: boolean
  className?: string
  contentClassName?: string
  allowCustomOption?: boolean
  customOptionLabel?: string
}

type SingleComboboxDropdownProps = BaseComboboxDropdownProps & {
  multiple?: false
  value?: string
  defaultValue?: string
  onValueChange?: (value: string | undefined) => void
}

type MultiComboboxDropdownProps = BaseComboboxDropdownProps & {
  multiple: true
  value?: string[]
  defaultValue?: string[]
  onValueChange?: (value: string[]) => void
}

export type ComboboxDropdownProps =
  | SingleComboboxDropdownProps
  | MultiComboboxDropdownProps

export function ComboboxDropdown(props: ComboboxDropdownProps) {
  const {
    options,
    placeholder = 'Select option...',
    clearable = true,
    searchable = true,
    searchPlaceholder = 'Search...',
    emptyText = 'No results found.',
    disabled,
    isPending,
    className,
    contentClassName,
    allowCustomOption = false,
    customOptionLabel = 'Tambah',
  } = props

  const multiple = props.multiple === true
  const [open, setOpen] = React.useState(false)
  const [searchQuery, setSearchQuery] = React.useState('')
  const [singleValue, setSingleValue] = React.useState<string | undefined>(
    props.multiple === true ? undefined : props.defaultValue
  )
  const [multiValue, setMultiValue] = React.useState<string[]>(
    props.multiple === true ? (props.defaultValue ?? []) : []
  )

  const selected = multiple
    ? (props.value ?? multiValue)
    : (props.value ?? singleValue)

  const selectedSet = React.useMemo(
    () => new Set(multiple ? (selected as string[]) : selected ? [selected as string] : []),
    [multiple, selected]
  )

  const optionMap = React.useMemo(
    () => new Map(options.map((option) => [option.value, option.label])),
    [options]
  )

  const normalizedQuery = searchQuery.trim()
  const hasExactOption = React.useMemo(() => {
    if (!normalizedQuery) return false

    const lowerQuery = normalizedQuery.toLowerCase()
    return options.some((option) => {
      return (
        option.value.toLowerCase() === lowerQuery ||
        option.label.toLowerCase() === lowerQuery
      )
    })
  }, [normalizedQuery, options])

  const triggerLabel = React.useMemo(() => {
    if (!multiple) {
      const value = selected as string | undefined
      return value ? optionMap.get(value) ?? value : placeholder
    }

    const values = selected as string[]
    if (values.length === 0) return placeholder
    if (values.length <= 2) {
      return values.map((value) => optionMap.get(value) ?? value).join(', ')
    }
    return `${values.length} selected`
  }, [multiple, optionMap, placeholder, selected])

  function updateSingle(next: string | undefined) {
    if (props.multiple === true) return
    if (props.value === undefined) {
      setSingleValue(next)
    }
    props.onValueChange?.(next)
  }

  function updateMulti(next: string[]) {
    if (props.multiple !== true) return
    if (props.value === undefined) {
      setMultiValue(next)
    }
    props.onValueChange?.(next)
  }

  function toggleValue(value: string) {
    if (!multiple) {
      const current = selected as string | undefined
      const next = current === value && clearable ? undefined : value
      updateSingle(next)
      setOpen(false)
      return
    }

    const current = selected as string[]
    const exists = current.includes(value)
    const next = exists
      ? current.filter((item) => item !== value)
      : [...current, value]
    updateMulti(next)
  }

  function clearSelection() {
    if (multiple) {
      updateMulti([])
      return
    }
    updateSingle(undefined)
    setOpen(false)
  }

  function selectCustomOption() {
    if (!allowCustomOption || !normalizedQuery) return

    if (!multiple) {
      updateSingle(normalizedQuery)
      setOpen(false)
      return
    }

    const current = selected as string[]
    if (current.includes(normalizedQuery)) return
    updateMulti([...current, normalizedQuery])
  }

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button
          variant='outline'
          role='combobox'
          aria-expanded={open}
          disabled={disabled}
          className={cn(
            'w-full justify-between font-normal text-foreground dark:text-foreground [&_svg]:text-foreground dark:[&_svg]:text-foreground',
            className
          )}
        >
          <span className='truncate text-start text-foreground dark:text-foreground'>{triggerLabel}</span>
          <ChevronDownIcon className='ms-2 h-4 w-4 shrink-0 opacity-50 text-foreground dark:text-foreground' />
        </Button>
      </PopoverTrigger>
      <PopoverContent className={cn('w-[--radix-popover-trigger-width] p-0', contentClassName)} align='start'>
        <Command>
          {searchable && (
            <CommandInput
              value={searchQuery}
              onValueChange={setSearchQuery}
              placeholder={searchPlaceholder}
            />
          )}
          <div
            className='max-h-[300px] overflow-y-auto overscroll-contain'
            onWheelCapture={(event) => event.stopPropagation()}
          >
            <CommandList className='max-h-none overflow-visible'>
              {isPending ? (
                <div className='flex items-center justify-center gap-2 px-3 py-6 text-sm text-muted-foreground'>
                  <Loader2Icon className='h-4 w-4 animate-spin' />
                  Loading...
                </div>
              ) : (
                <>
                  <CommandEmpty>{emptyText}</CommandEmpty>
                  <CommandGroup>
                    {allowCustomOption && normalizedQuery && !hasExactOption && (
                      <CommandItem onSelect={selectCustomOption}>
                        <span>{customOptionLabel} "{normalizedQuery}"</span>
                      </CommandItem>
                    )}
                    {options.map((option) => {
                      const isSelected = selectedSet.has(option.value)
                      return (
                        <CommandItem
                          key={option.value}
                          disabled={option.disabled}
                          onSelect={() => toggleValue(option.value)}
                        >
                          <span>{option.label}</span>
                          <CheckIcon
                            className={cn(
                              'ms-auto h-4 w-4',
                              isSelected ? 'opacity-100' : 'opacity-0'
                            )}
                          />
                        </CommandItem>
                      )
                    })}
                  </CommandGroup>
                  {clearable && selectedSet.size > 0 && (
                    <>
                      <CommandSeparator />
                      <CommandGroup>
                        <CommandItem onSelect={clearSelection} className='justify-center'>
                          Clear selection
                        </CommandItem>
                      </CommandGroup>
                    </>
                  )}
                </>
              )}
            </CommandList>
          </div>
        </Command>
      </PopoverContent>
    </Popover>
  )
}
