'use client'

import { cva, VariantProps } from 'class-variance-authority'
import { Check, ChevronsUpDown, X as RemoveIcon } from 'lucide-react'
// @ts-ignore Required to avoid TS error: The inferred type of MultiSelectorContent cannot be named without a reference to @radix-ui
import type { Popover as PopoverPrimitive } from 'radix-ui'
import React, { isValidElement, ReactElement, useEffect } from 'react'
import {
  Badge,
  cn,
  Command as Command,
  CommandEmpty as CommandEmpty,
  CommandInput as CommandInput,
  CommandItem as CommandItem,
  CommandList as CommandList,
  Popover,
  PopoverAnchor,
  PopoverContent,
  PopoverContentProps,
} from 'ui'
import { SIZE_VARIANTS, SIZE_VARIANTS_DEFAULT } from 'ui/src/lib/constants'

interface MultiSelectContextProps {
  id: string
  values: string[]
  onValuesChange: (value: string[]) => void
  toggleValue: (values: string) => void
  open: boolean
  setOpen: React.Dispatch<React.SetStateAction<boolean>>
  inputValue: string
  setInputValue: React.Dispatch<React.SetStateAction<string>>
  activeIndex: number
  setActiveIndex: React.Dispatch<React.SetStateAction<number>>
  size: MultiSelectorProps['size']
  disabled?: boolean
  dropdownMaxHeight: number
}

const MultiSelectContext = React.createContext<MultiSelectContextProps | null>(null)

const DROPDOWN_MAX_HEIGHT = 300
const DROPDOWN_GAP = 8

const commandItemClass = cn(
  'relative text-foreground-lighter text-left px-2 py-1.5 rounded-sm',
  'hover:text-foreground hover:!bg-overlay-hover w-full flex items-center space-x-2',
  'peer-data-[value=true]:bg-overlay-hover'
)

function useMultiSelect() {
  const context = React.useContext(MultiSelectContext)
  if (!context) {
    throw new Error('useMultiSelect must be used within a MultiSelectProvider')
  }
  return context
}

const MultiSelectorVariants = cva('', {
  variants: {
    size: {
      ...SIZE_VARIANTS,
    },
  },
  defaultVariants: {
    size: SIZE_VARIANTS_DEFAULT,
  },
})

type MultiSelectorMode = 'combobox' | 'inline-combobox'

type MultiSelectorProps = {
  mode?: MultiSelectorMode
  values: string[]
  onValuesChange: (value: string[]) => void
  disabled?: boolean
} & React.ComponentPropsWithoutRef<typeof Command> &
  VariantProps<typeof MultiSelectorVariants>

function MultiSelector({
  values = [],
  onValuesChange,
  disabled,
  dir,
  size,
  className,
  children,
  id: idProp,
  ...props
}: MultiSelectorProps) {
  const ref = React.useRef(null)
  const [open, setOpen] = React.useState<boolean>(false)
  const [inputValue, setInputValue] = React.useState<string>('')
  const [activeIndex, setActiveIndex] = React.useState<number>(-1)
  const [dropdownMaxHeight, setDropdownMaxHeight] = React.useState<number>(DROPDOWN_MAX_HEIGHT)
  const generatedId = React.useId()
  const id = idProp ?? generatedId

  const toggleValue = React.useCallback(
    (toggledValue: string) => {
      if (values.includes(toggledValue)) {
        onValuesChange(values.filter((value) => value !== toggledValue) || [])
      } else {
        onValuesChange([...values, toggledValue])
      }
    },
    [values]
  )

  useEffect(() => {
    if (!open) return
    const controller = new AbortController()
    const { signal } = controller

    const updateDropdownMetrics = () => {
      if (typeof window === 'undefined') return
      const triggerEl = ref.current as HTMLDivElement | null
      if (!triggerEl) return

      const rect = triggerEl.getBoundingClientRect()
      const viewportHeight = window.innerHeight
      const spaceBelow = viewportHeight - rect.bottom - DROPDOWN_GAP
      const spaceAbove = rect.top - DROPDOWN_GAP
      const shouldDropUp = spaceBelow < DROPDOWN_MAX_HEIGHT && spaceAbove > spaceBelow
      const placement = shouldDropUp ? 'top' : 'bottom'
      const availableSpace = Math.max(placement === 'top' ? spaceAbove : spaceBelow, 0)
      const nextHeight =
        availableSpace > 0 ? Math.min(DROPDOWN_MAX_HEIGHT, availableSpace) : DROPDOWN_MAX_HEIGHT

      setDropdownMaxHeight(nextHeight)
    }

    const handleUpdate = updateDropdownMetrics
    handleUpdate()
    window.addEventListener('resize', handleUpdate, { signal })
    window.addEventListener('scroll', handleUpdate, { capture: true, passive: true, signal })

    return () => controller.abort()
  }, [open])

  const handleKeyDown = React.useCallback(
    (e: React.KeyboardEvent<HTMLDivElement>) => {
      switch (e.key) {
        case 'Backspace':
        case 'Delete':
          if (values.length > 0 && inputValue.length === 0) {
            if (activeIndex !== -1 && activeIndex < values.length) {
              onValuesChange(values.filter((item) => item !== values[activeIndex]))
              const newIndex = activeIndex - 1 < 0 ? 0 : activeIndex - 1
              setActiveIndex(newIndex)
            } else {
              onValuesChange(values.filter((item) => item !== values[values.length - 1]))
            }
          }
          break
        case 'Escape':
          activeIndex !== -1 ? setActiveIndex(-1) : setOpen(false)
          if (ref.current) {
            const button = (ref.current as HTMLDivElement).querySelector('button[role="combobox"]')
            button && (button as HTMLButtonElement).focus()
          }
          break
        case 'Enter':
          setOpen(true)
          break
      }
    },
    [values, inputValue, activeIndex]
  )

  return (
    <MultiSelectContext.Provider
      value={{
        id,
        values,
        toggleValue,
        onValuesChange,
        open,
        setOpen,
        inputValue,
        setInputValue,
        activeIndex,
        setActiveIndex,
        size: size || 'small',
        disabled,
        dropdownMaxHeight,
      }}
    >
      <Popover open={open} onOpenChange={setOpen}>
        <Command
          id={id}
          ref={ref}
          onKeyDown={handleKeyDown}
          className={cn('relative w-auto overflow-visible bg-transparent flex flex-col', className)}
          dir={dir}
          {...props}
        >
          {children}
        </Command>
      </Popover>
    </MultiSelectContext.Provider>
  )
}

export interface MultiSelectorTriggerProps extends React.HTMLAttributes<HTMLButtonElement> {
  label?: string
  persistLabel?: boolean
  className?: string
  badgeLimit?: number | 'wrap'
  deletableBadge?: boolean
  showIcon?: boolean
  mode?: MultiSelectorMode
}

const MultiSelectorTrigger = React.forwardRef<HTMLButtonElement, MultiSelectorTriggerProps>(
  (
    {
      label,
      persistLabel = false,
      className,
      deletableBadge = true,
      badgeLimit = 9999,
      showIcon = true,
      mode = 'combobox',
      children,
      ...props
    },
    ref
  ) => {
    const { activeIndex, values, setInputValue, toggleValue, disabled, open, setOpen } =
      useMultiSelect()

    const inputRef = React.useRef<HTMLButtonElement>(null)

    // Use the provided ref if available, otherwise use the local ref
    React.useImperativeHandle(ref, () => inputRef.current as HTMLButtonElement)
    const inlineInputRef = React.useRef<HTMLInputElement>(null)
    const badgesRef = React.useRef<HTMLDivElement>(null)

    const [visibleBadges, setVisibleBadges] = React.useState<string[]>([])
    const [extraBadgesCount, setExtraBadgesCount] = React.useState(0)
    const [isDeleteHovered, setIsDeleteHovered] = React.useState(false)

    const IS_BADGE_LIMIT_WRAP = badgeLimit === 'wrap'
    const IS_NUMERIC_LIMIT = typeof badgeLimit === 'number'
    const IS_INLINE_MODE = mode === 'inline-combobox'

    React.useEffect(() => {
      if (!inputRef?.current || !badgesRef.current) return

      if (IS_BADGE_LIMIT_WRAP) {
        setVisibleBadges(values)
        setExtraBadgesCount(0)
      } else {
        setVisibleBadges(values.slice(0, badgeLimit))
        setExtraBadgesCount(Math.max(0, values.length - badgeLimit))
      }
    }, [values, badgeLimit])

    const badgeClasses = 'rounded-sm shrink-0 px-1.5'

    const handleTriggerClick: React.MouseEventHandler<HTMLButtonElement> = React.useCallback(
      (event) => {
        if (IS_INLINE_MODE) {
          event.stopPropagation()
          event.preventDefault()

          if (!open) {
            setInputValue('')
          }

          setTimeout(() => {
            inlineInputRef.current?.focus()
          }, 100)

          return
        }

        const willOpen = !open
        setOpen(willOpen)
        if (willOpen) setInputValue('')
      },
      [open, setOpen, setInputValue, IS_INLINE_MODE]
    )

    return (
      <PopoverAnchor asChild>
        <button
          ref={inputRef}
          onClick={(e) => !isDeleteHovered && handleTriggerClick(e)}
          disabled={disabled}
          type="button"
          role="combobox"
          className={cn(
            'flex w-full min-w-[200px] min-h-[40px] items-center justify-between rounded-md border',
            'border-alternative bg-control px-3 py-2 text-sm',
            'ring-offset-background placeholder:text-muted-foreground',
            'focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2',
            'disabled:cursor-not-allowed disabled:opacity-50',
            'hover:border-primary transition-colors duration-200',
            className
          )}
          {...props}
        >
          <div
            ref={badgesRef}
            className={cn(
              'flex gap-1 -ml-1 overflow-hidden flex-1',
              IS_BADGE_LIMIT_WRAP && 'flex-wrap',
              !IS_BADGE_LIMIT_WRAP &&
                'overflow-x-auto scrollbar-thin scrollbar-track-transparent transition-colors scrollbar-thumb-muted-foreground dark:scrollbar-thumb-muted scrollbar-thumb-rounded-lg'
            )}
          >
            {visibleBadges.map((value) => (
              <Badge key={value} className={badgeClasses}>
                {value}
                {deletableBadge && (
                  <div
                    onMouseEnter={() => setIsDeleteHovered(true)}
                    onMouseLeave={() => setIsDeleteHovered(false)}
                    onClick={(e) => {
                      e.stopPropagation()
                      toggleValue(value)
                      setIsDeleteHovered(false)
                    }}
                    className="ml-1 text-foreground-lighter hover:text-foreground-light transition-colors pointer-events-auto"
                  >
                    <RemoveIcon size={12} />
                  </div>
                )}
              </Badge>
            ))}
            {extraBadgesCount > 0 && (
              <Badge className={badgeClasses}>
                {IS_NUMERIC_LIMIT && badgeLimit < 1
                  ? `${extraBadgesCount} item${extraBadgesCount > 1 ? 's' : ''} selected`
                  : `+${extraBadgesCount}`}
              </Badge>
            )}
            <span
              className={cn(
                'text-foreground-muted whitespace-nowrap leading-5.5 ml-1 opacity-0 transition-opacity hidden',
                !IS_INLINE_MODE &&
                  (persistLabel || values.length === 0) &&
                  'opacity-100 visible inline'
              )}
            >
              {label}
            </span>
            {IS_INLINE_MODE && (
              <MultiSelectorInput
                ref={inlineInputRef}
                showSearchIcon={false}
                onValueChange={activeIndex === -1 ? setInputValue : undefined}
                placeholder={label}
                autoFocus={false}
                wrapperClassName={cn(
                  'px-0 flex-1 border-none truncate',
                  IS_BADGE_LIMIT_WRAP && 'min-w-[85px]'
                )}
                className="py-0 px-1 truncate"
              />
            )}
          </div>

          {showIcon && (
            <ChevronsUpDown
              size={16}
              strokeWidth={2}
              className="text-foreground-lighter shrink-0 ml-1.5"
            />
          )}
        </button>
      </PopoverAnchor>
    )
  }
)

MultiSelectorTrigger.displayName = 'MultiSelectorTrigger'
MultiSelector.Trigger = MultiSelectorTrigger

const MultiSelectorInputVariants = cva('bg-control border', {
  variants: {
    size: {
      ...SIZE_VARIANTS,
    },
  },
  defaultVariants: {
    size: SIZE_VARIANTS_DEFAULT,
  },
})

const getInputId = (id: string) => `${id}-input`

const MultiSelectorInput = React.forwardRef<
  React.ElementRef<typeof CommandInput>,
  React.ComponentPropsWithoutRef<typeof CommandInput> & {
    showResetIcon?: boolean
    showSearchIcon?: boolean
    wrapperClassName?: string
  }
>(({ className, wrapperClassName, showResetIcon, showSearchIcon, ...props }, ref) => {
  const {
    id,
    open,
    setOpen,
    inputValue,
    setInputValue,
    activeIndex,
    setActiveIndex,
    size,
    disabled,
  } = useMultiSelect()
  const inputRef = React.useRef<HTMLInputElement>(null)

  // Use the provided ref if available, otherwise use the local ref
  React.useImperativeHandle(ref, () => inputRef.current as HTMLInputElement)

  const handleFocus = () => !open && setOpen(true)
  const handleClick = () => setActiveIndex(-1)
  const handleReset = () => {
    setInputValue('')
    setInputFocus()
  }

  const setInputFocus = () => {
    setTimeout(() => {
      if (!inputRef?.current) return
      if (open) {
        inputRef.current.focus()
      }
    }, 100)
  }

  useEffect(() => {
    setInputFocus()

    if (!open) {
      inputRef.current?.blur()
    }
  }, [open])

  return (
    <CommandInput
      ref={inputRef}
      value={inputValue}
      onValueChange={activeIndex === -1 ? setInputValue : undefined}
      onFocus={handleFocus}
      onClick={handleClick}
      tabIndex={open ? 0 : -1}
      disabled={disabled}
      showSearchIcon={showSearchIcon}
      showResetIcon={showResetIcon}
      handleReset={handleReset}
      wrapperClassName={wrapperClassName}
      className={cn(
        MultiSelectorInputVariants({ size }),
        'text-sm bg-transparent h-full grow border-none outline-hidden placeholder:text-foreground-muted flex-1',
        activeIndex !== -1 && 'caret-transparent',
        className
      )}
      // Can't use id as CommandInput overrides it
      data-id={getInputId(id)}
      {...props}
    />
  )
})

MultiSelectorInput.displayName = 'MultiSelectorInput'
MultiSelector.Input = MultiSelectorInput

const MultiSelectorContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
  ({ className, children, ...props }, ref) => {
    const { id } = useMultiSelect()
    return (
      <PopoverContent
        align="start"
        ref={ref}
        className={cn(
          'bg-overlay shadow-md z-50 border border-overlay rounded-md p-0',
          'w-(--radix-popper-anchor-width)',
          className
        )}
        onFocusOutside={(event) => {
          if (event.target instanceof HTMLElement && event.target.dataset.id === getInputId(id)) {
            event.preventDefault()
            event.stopPropagation()
          }
        }}
        sameWidthAsTrigger
        {...props}
      >
        {children}
      </PopoverContent>
    )
  }
)

MultiSelectorContent.displayName = 'MultiSelectorContent'
MultiSelector.Content = MultiSelectorContent

const MultiSelectorList = React.forwardRef<
  React.ElementRef<typeof CommandList>,
  React.ComponentPropsWithoutRef<typeof CommandList> & {
    creatable?: boolean
  }
>(({ className, children, creatable = false }, ref) => {
  const { open, inputValue, setInputValue, toggleValue, dropdownMaxHeight } = useMultiSelect()

  const options = !!children
    ? Array.isArray(children)
      ? (children as React.ReactNode[])
      : typeof children === 'object' &&
          'props' in children &&
          isValidElement<{ children: ReactElement[] }>(children)
        ? children.props.children
        : []
    : []
  const availableOptions = options
    .filter((x: any) => !!x.props.value)
    .map((x: any) => x.props.value.toLowerCase())
  const isOptionExists = availableOptions.some((x: string) => x === inputValue.toLowerCase())

  return (
    <CommandList
      ref={ref}
      className={cn(
        'p-2 flex flex-col gap-2 scrollbar-thin scrollbar-track-transparent transition-colors',
        'scrollbar-thumb-muted-foreground dark:scrollbar-thumb-muted',
        'scrollbar-thumb-rounded-lg w-full overflow-y-auto',
        className
      )}
      style={{ maxHeight: dropdownMaxHeight }}
      onWheel={(e) => e.stopPropagation()}
    >
      {children}
      {creatable && inputValue.length > 0 && !isOptionExists ? (
        <CommandItem
          role="option"
          onSelect={() => {
            open && toggleValue(inputValue)
            setInputValue('')
          }}
          className={commandItemClass}
        >
          Create "{inputValue}"
        </CommandItem>
      ) : creatable && options.length === 0 ? (
        <div className="p-2 py-1.5 text-xs text-foreground-lighter font-italic">
          Type to add a value
        </div>
      ) : (
        <CommandEmpty>
          <span className="text-foreground-muted">No results found</span>
        </CommandEmpty>
      )}
    </CommandList>
  )
})

MultiSelectorList.displayName = 'MultiSelectorList'
MultiSelector.List = MultiSelectorList

const MultiSelectorItem = React.forwardRef<
  HTMLDivElement,
  { value: string } & React.ComponentPropsWithoutRef<typeof CommandItem>
>(({ className, value, children, ...props }, ref) => {
  const { values: selectedValues, setInputValue, toggleValue, open } = useMultiSelect()
  const isSelected = selectedValues.includes(value)

  return (
    <CommandItem
      ref={ref}
      tabIndex={open ? 0 : -1}
      role="option"
      onSelect={() => {
        open && toggleValue(value)
        setInputValue('')
      }}
      className={cn(commandItemClass, className)}
      {...props}
    >
      <div
        className={cn(
          'flex items-center justify-center',
          'peer h-4 w-4 shrink-0 rounded-sm border border-control bg-control/25 ring-offset-background',
          'transition-colors duration-150 ease-in-out',
          'hover:border-strong',
          'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
          'disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-foreground data-[state=checked]:text-background',
          isSelected ? 'bg-foreground text-background' : '[&_svg]:invisible'
        )}
      >
        <Check className="h-3 w-3" strokeWidth={4} />
      </div>
      <div className="text-xs grow leading-none pointer-events-none cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:pointer-events-none peer-disabled:opacity-50">
        {children}
      </div>
    </CommandItem>
  )
})

MultiSelectorItem.displayName = 'MultiSelectorItem'
MultiSelector.Item = MultiSelectorItem

export {
  MultiSelector,
  MultiSelectorContent,
  MultiSelectorInput,
  MultiSelectorItem,
  MultiSelectorList,
  MultiSelectorTrigger,
}
