'use client'

import { Combobox as ComboboxPrimitive } from '@base-ui/react'
import { createContext, useContext, useRef, useState } from 'react'
import type * as React from 'react'
import {
  SvgCheck,
  SvgDelete1,
  SvgTaillessLineArrowDown1,
} from '@chainlink/blocks-icons'
import { SvgSearch } from '../../icons'
import { cn } from '../../utils'
import {
  DROPDOWN_EMPTY_STATE_HEIGHT,
  DROPDOWN_ELEMENT_HEIGHT,
  DROPDOWN_LIST_MAX_HEIGHT,
  type DropdownSize,
} from '../dropdownHeights'
import { inputVariants } from '../Input'
import { ClearIcon } from '../Input/icons'
import {
  getDropdownItemBaseClassName,
  getDropdownItemIndicatorOffsetClassName,
  getInputControlContentTextClassName,
  getInputControlTextClassName,
  getInputControlValueSlotClassName,
} from '../inputControlStyles'
import {
  useScrollArrows,
  ScrollArrowUp,
  ScrollArrowDown,
} from '../ScrollArrows'

type ComboboxRootProps = Omit<
  React.ComponentProps<typeof ComboboxPrimitive.Root>,
  'itemToStringValue'
> & {
  itemToStringValue?: (itemValue: any) => string
  /** When false, the hover clear control is not shown for single-select (default true). */
  clearable?: boolean
}
type ComboboxOnValueChange = NonNullable<ComboboxRootProps['onValueChange']>
type ComboboxChangeEventDetails = Parameters<ComboboxOnValueChange>[1]
type BaseUiMouseEvent<T extends Element = HTMLElement> = React.MouseEvent<T> & {
  preventBaseUIHandler: () => void
}
type ComboboxSetValue = (
  value: unknown,
  eventDetails?: ComboboxChangeEventDetails,
) => void

const COMBOBOX_SELECT_ALL_VALUE = Symbol('combobox-select-all')

const createComboboxChangeEventDetails = (): ComboboxChangeEventDetails => {
  const eventDetails: ComboboxChangeEventDetails = {
    reason: 'none',
    event: new Event('change'),
    cancel: () => {
      eventDetails.isCanceled = true
    },
    allowPropagation: () => {
      eventDetails.isPropagationAllowed = true
    },
    isCanceled: false,
    isPropagationAllowed: false,
    trigger: undefined,
  }

  return eventDetails
}

const ComboboxContentRootRefContext =
  createContext<React.RefObject<HTMLDivElement | null> | null>(null)

const ComboboxSizeContext = createContext<DropdownSize>('default')

const ComboboxMultipleContext = createContext<boolean>(false)

/** Chip/trigger size for multi-select badges (follows trigger size). */
const ComboboxTriggerSizeContext = createContext<DropdownSize>('default')

/** Used internally so TagsValue can clear overflow without a callback (e.g. "+N more" chip). */
const ComboboxSetValueContext = createContext<ComboboxSetValue | null>(null)

/** Used by List to render default Select All when multiple (value, items, setValue). */
const ComboboxMultiListContext = createContext<{
  value: unknown
  items: unknown[]
  setValue: ComboboxSetValue | null
} | null>(null)

const ComboboxDisabledContext = createContext<boolean>(false)

const ComboboxClearableContext = createContext<boolean>(true)

/**
 * Root component. Manages open state, search filtering, and the selected value.
 * Supports both single-select and multi-select (`multiple`) modes.
 * Provides `size`, `disabled`, and `clearable` context to all child components.
 */
function Combobox(props: ComboboxRootProps & { size?: DropdownSize }) {
  const {
    multiple = false,
    value: valueProp,
    items,
    onValueChange,
    defaultValue,
    disabled = false,
    clearable = true,
    size = 'default',
    ...rest
  } = props
  const [internalValue, setInternalValue] = useState<unknown>(() =>
    multiple ? (defaultValue ?? []) : (defaultValue ?? null),
  )
  const isControlled = valueProp !== undefined
  const value = isControlled ? valueProp : internalValue
  const handleValueChange = (
    nextValue: unknown,
    eventDetails: ComboboxChangeEventDetails,
  ) => {
    if (!isControlled) {
      if (multiple) {
        const next = Array.isArray(nextValue) ? nextValue : []
        setInternalValue((prev: unknown) => {
          if (
            Array.isArray(prev) &&
            prev.length === next.length &&
            prev.every((item, index) => item === next[index])
          ) {
            return prev
          }

          return next
        })
      } else {
        setInternalValue(nextValue)
      }
    }

    onValueChange?.(nextValue as never, eventDetails)
  }
  const setValue: ComboboxSetValue | null =
    isControlled && onValueChange == null
      ? null
      : (nextValue, eventDetails = createComboboxChangeEventDetails()) =>
          handleValueChange(nextValue, eventDetails)
  const multiListContext =
    multiple && (value !== undefined || items !== undefined)
      ? {
          value: value ?? [],
          items: Array.isArray(items) ? [...items] : [],
          setValue,
        }
      : null
  return (
    <ComboboxSizeContext.Provider value={size}>
      <ComboboxClearableContext.Provider value={clearable}>
        <ComboboxMultipleContext.Provider value={multiple}>
          <ComboboxSetValueContext.Provider value={setValue}>
            <ComboboxMultiListContext.Provider value={multiListContext}>
              <ComboboxDisabledContext.Provider value={disabled}>
                <ComboboxPrimitive.Root
                  multiple={multiple}
                  value={value}
                  items={items}
                  onValueChange={handleValueChange as ComboboxOnValueChange}
                  defaultValue={defaultValue}
                  disabled={disabled}
                  {...rest}
                />
              </ComboboxDisabledContext.Provider>
            </ComboboxMultiListContext.Provider>
          </ComboboxSetValueContext.Provider>
        </ComboboxMultipleContext.Provider>
      </ComboboxClearableContext.Provider>
    </ComboboxSizeContext.Provider>
  )
}

/** Renders the selected value or placeholder inside `ComboboxTrigger`. Pass a render function as `children` to display custom content such as an icon + label pair. */
function ComboboxValue({
  children,
  placeholder,
  className,
  ...props
}: ComboboxPrimitive.Value.Props & { className?: string }) {
  return (
    <ComboboxPrimitive.Value {...props}>
      {(value: any) => {
        const isEmpty =
          value == null || (Array.isArray(value) && value.length === 0)
        const content = isEmpty
          ? placeholder
          : typeof children === 'function'
            ? children(value)
            : typeof value === 'string'
              ? value
              : (value?.label ?? String(value))
        return (
          <span
            data-slot="combobox-value"
            data-placeholder={isEmpty ? 'true' : undefined}
            className={cn(
              '!leading-inherit flex flex-row items-center gap-2 truncate !text-inherit',
              isEmpty && '!text-input-muted-foreground',
              className,
            )}
          >
            {content}
          </span>
        )
      }}
    </ComboboxPrimitive.Value>
  )
}

/**
 * The button that opens the combobox. Styled as a Blocks input.
 * Includes the clear button and caret icon automatically.
 * Pass `hideIcon` to remove the caret, or `width` to control sizing behaviour.
 */
function ComboboxTrigger({
  className,
  children,
  disabled,
  hideIcon = false,
  width,
  ...props
}: ComboboxPrimitive.Trigger.Props & {
  /** Hides the dropdown caret arrow. */
  hideIcon?: boolean
  /** Controls trigger width: `'full'` fills the container, `'hug'` shrinks to content, `'responsive'` adapts to the viewport. */
  width?: 'responsive' | 'hug' | 'full'
}) {
  const size = useContext(ComboboxSizeContext) || 'default'
  const multiple = useContext(ComboboxMultipleContext)
  const clearable = useContext(ComboboxClearableContext)

  return (
    <ComboboxPrimitive.Trigger
      data-slot="combobox-trigger"
      disabled={disabled}
      className={cn(
        'group',
        inputVariants({ size, width }),
        'flex items-center justify-between gap-2 whitespace-nowrap text-foreground',
        '[&>:first-child]:min-w-0 [&>:first-child]:flex-1',
        'hover:border-input-border-hover data-[popup-open]:border-input-border-active [&.hover]:border-input-border-hover',
        'data-[disabled]:hover:border-input-border-disabled',
        'data-[disabled]:hover:bg-input-disabled',
        '[&[data-disabled].hover]:border-input-border-disabled',
        '[&[data-disabled].hover]:bg-input-disabled',
        '[&_[data-slot=combobox-value]]:text-foreground',
        '[&_[data-slot=combobox-value][data-placeholder=true]]:!text-input-muted-foreground',
        '[&[data-disabled]_[data-slot=combobox-value]]:!text-input-muted-more-foreground',
        '[&[data-disabled]_[data-slot=combobox-value][data-placeholder=true]]:!text-input-muted-more-foreground',
        '[&[data-disabled]_[data-slot=combobox-badge]]:!text-input-muted-more-foreground',
        '[&[data-disabled]_[data-slot=combobox-badge]_span]:!text-input-muted-more-foreground',
        '[&[data-disabled]_[data-slot=combobox-badge]_img]:opacity-60',
        '[&[data-disabled]_[data-slot=combobox-badge]]:border-input-border-disabled',
        '[&[data-disabled]_[data-slot=combobox-badge]]:bg-input-disabled',
        getInputControlValueSlotClassName('combobox-value', size),
        'data-[disabled]:cursor-not-allowed data-[disabled]:border-input-border-disabled data-[disabled]:bg-input-disabled data-[disabled]:!text-input-muted-more-foreground',
        className,
      )}
      style={{
        height: DROPDOWN_ELEMENT_HEIGHT[size],
        minHeight: DROPDOWN_ELEMENT_HEIGHT[size],
      }}
      {...props}
    >
      <ComboboxTriggerSizeContext.Provider value={size}>
        {children}
      </ComboboxTriggerSizeContext.Provider>
      <div className="flex items-center gap-2">
        {!multiple && clearable && (
          <span
            className="flex items-center group-data-[disabled]:hidden"
            onClick={(e) => e.stopPropagation()}
            onMouseDown={(e) => e.stopPropagation()}
          >
            <ComboboxClear disabled={disabled} />
          </span>
        )}
        {!hideIcon && (
          <div
            data-slot="combobox-icon"
            className={cn(
              'text-input-muted-foreground transition-transform duration-100',
              'group-data-[popup-open]:rotate-180',
              size === 'default' ? 'size-4' : 'size-3',
              '[&_svg]:h-full [&_svg]:w-full [&_svg]:text-input-muted-foreground',
              'group-data-[disabled]:[&_svg]:!text-input-muted-more-foreground',
            )}
          >
            <SvgTaillessLineArrowDown1 />
          </div>
        )}
      </div>
    </ComboboxPrimitive.Trigger>
  )
}

/** The ×-button that clears the current selection. Rendered automatically inside `ComboboxTrigger`; returns `null` when `disabled`. */
function ComboboxClear({
  className,
  disabled,
  ...props
}: ComboboxPrimitive.Clear.Props) {
  if (disabled) return null
  return (
    <ComboboxPrimitive.Clear
      nativeButton={false}
      render={<span />}
      data-slot="combobox-clear"
      className={cn(
        'pointer-events-none cursor-pointer opacity-0 transition-opacity',
        'group-hover:pointer-events-auto group-hover:opacity-100',
        'focus:pointer-events-auto focus:opacity-100',
        'text-input-muted-foreground hover:text-muted-foreground',
        'focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
        'size-4 [&_svg]:h-full [&_svg]:w-full',
        className,
      )}
      {...props}
    >
      <ClearIcon />
    </ComboboxPrimitive.Clear>
  )
}

/**
 * The dropdown panel. Renders inside a portal and positions relative to the trigger.
 * Contains the search input, item list, and empty state. Use `side`, `align`, and
 * `sideOffset` to customise positioning.
 */
function ComboboxContent({
  className,
  children,
  side = 'bottom',
  sideOffset = 4,
  align = 'start',
  alignOffset = 0,
  anchor,
  container,
  ...props
}: ComboboxPrimitive.Popup.Props &
  Pick<
    ComboboxPrimitive.Positioner.Props,
    'side' | 'align' | 'sideOffset' | 'alignOffset' | 'anchor'
  > & {
    container?: HTMLElement | null | React.RefObject<HTMLElement | null>
  }) {
  const size = useContext(ComboboxSizeContext)
  const contentRootRef = useRef<HTMLDivElement>(null)
  return (
    <ComboboxContentRootRefContext.Provider value={contentRootRef}>
      <ComboboxPrimitive.Portal container={container}>
        <ComboboxPrimitive.Positioner
          side={side}
          sideOffset={sideOffset}
          align={align}
          alignOffset={alignOffset}
          anchor={anchor}
          className="isolate z-50"
        >
          <ComboboxPrimitive.Popup
            data-slot="combobox-content"
            className={cn(
              'group/combobox-content relative',
              getInputControlTextClassName(size),
              getInputControlContentTextClassName(size),
              'text-muted-foreground',
              'overflow-hidden rounded border border-solid border-input-border-active bg-background p-0',
              'w-[var(--anchor-width)] max-w-[var(--anchor-width)]',
              'max-h-[var(--available-height)]',
              'origin-[var(--transform-origin)]',
              'data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95',
              'data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95',
              'data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2',
              'data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2',
              className,
            )}
            {...props}
          >
            <div ref={contentRootRef}>{children}</div>
          </ComboboxPrimitive.Popup>
        </ComboboxPrimitive.Positioner>
      </ComboboxPrimitive.Portal>
    </ComboboxContentRootRefContext.Provider>
  )
}

/** The search text field inside the dropdown. Base UI filters the item list automatically as the user types. */
function ComboboxInput({
  className,
  ...props
}: Omit<ComboboxPrimitive.Input.Props, 'size'>) {
  const size = useContext(ComboboxSizeContext)
  return (
    <div
      className="flex items-center border-b border-input-border-active px-3"
      style={{
        boxSizing: 'border-box',
        height: DROPDOWN_ELEMENT_HEIGHT[size],
        minHeight: DROPDOWN_ELEMENT_HEIGHT[size],
      }}
    >
      <SvgSearch className="mr-2 h-4 w-4 shrink-0 text-input-muted-foreground" />
      <ComboboxPrimitive.Input
        data-slot="combobox-input"
        className={cn(
          getInputControlTextClassName(size),
          'flex w-full rounded-md border-none bg-transparent px-0 text-foreground outline-none placeholder:text-input-muted-foreground focus:border-none focus:shadow-none focus:outline-none focus-visible:shadow-none disabled:cursor-not-allowed disabled:opacity-50',
          size === 'default' ? 'py-3' : size === 'sm' ? 'py-2' : 'py-1.5',
          className,
        )}
        style={{
          boxSizing: 'border-box',
          height: DROPDOWN_ELEMENT_HEIGHT[size],
          minHeight: DROPDOWN_ELEMENT_HEIGHT[size],
        }}
        {...props}
      />
    </div>
  )
}

const resolveComboboxSize = (size: unknown): DropdownSize =>
  size === 'sm' || size === 'xs' ? size : 'default'

/**
 * The scrollable list of items. Accepts a render function for item rendering.
 * When `multiple`, automatically prepends a Select All row unless `hideSelectAll` is set.
 * Supports scroll arrows and an optional `header` above the list.
 */
function ComboboxList({
  className,
  children,
  header,
  items,
  hideSelectAll = false,
  ...props
}: ComboboxPrimitive.List.Props & {
  header?: React.ReactNode
  /** Options list (same as Root's `items`). When not passed, uses Root's items from context. When `multiple` and list has ≥1 item, Select All is shown unless `hideSelectAll`. */
  items?: unknown[]
  /** When `true`, hides the default Select All row in multi-select mode. */
  hideSelectAll?: boolean
}) {
  const size = useContext(ComboboxSizeContext)
  const contentRootRef = useContext(ComboboxContentRootRefContext)
  const multiple = useContext(ComboboxMultipleContext)
  const multiListContext = useContext(ComboboxMultiListContext)
  const {
    scrollRef,
    containerRef,
    showScrollUp,
    showScrollDown,
    handleScroll,
    startAutoScroll,
    stopAutoScroll,
    scrollToStart,
    scrollToEnd,
  } = useScrollArrows({ focusScopeRef: contentRootRef ?? undefined })

  const listItems =
    (Array.isArray(items) ? items : multiListContext?.items) ?? []
  const resolvedSize = resolveComboboxSize(size)
  const hasItems = listItems.length > 0
  const selectAllItem = multiple &&
    !hideSelectAll &&
    hasItems &&
    multiListContext?.setValue && (
      <ComboboxSelectAll
        checked={
          Array.isArray(multiListContext.value) &&
          multiListContext.value.length === listItems.length
        }
        onToggle={() => {
          if (!multiListContext.setValue) return
          const isAllSelected =
            Array.isArray(multiListContext.value) &&
            multiListContext.value.length === listItems.length
          multiListContext.setValue(isAllSelected ? [] : listItems)
        }}
      />
    )
  const resolvedChildren =
    typeof children === 'function' ? (
      <ComboboxPrimitive.Collection>{children}</ComboboxPrimitive.Collection>
    ) : (
      children
    )

  return (
    <div
      ref={containerRef as React.RefObject<HTMLDivElement>}
      className="relative"
    >
      <div
        ref={scrollRef as React.RefObject<HTMLDivElement>}
        onScroll={handleScroll}
        tabIndex={-1}
        className={cn(
          'overflow-y-auto overscroll-contain outline-none',
          '[scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
          showScrollUp && 'pt-6',
          showScrollDown && 'pb-6',
          header &&
            '[&:has([data-slot=combobox-list][data-empty])_.combobox-list-header]:hidden',
        )}
        style={{
          maxHeight:
            DROPDOWN_LIST_MAX_HEIGHT[resolvedSize] +
            (header ? DROPDOWN_ELEMENT_HEIGHT[resolvedSize] : 0) +
            (selectAllItem ? DROPDOWN_ELEMENT_HEIGHT[resolvedSize] : 0),
        }}
      >
        {header ? <div className="combobox-list-header">{header}</div> : null}
        <ComboboxPrimitive.List
          data-slot="combobox-list"
          className={cn(
            'data-[empty]:p-0 data-[empty]:[&_[data-slot=combobox-select-all]]:hidden',
            className,
          )}
          {...props}
        >
          {selectAllItem}
          {resolvedChildren}
        </ComboboxPrimitive.List>
      </div>
      {showScrollUp && (
        <ScrollArrowUp
          onHoverStart={() => startAutoScroll('up')}
          onHoverEnd={stopAutoScroll}
          onJump={scrollToStart}
        />
      )}
      {showScrollDown && (
        <ScrollArrowDown
          onHoverStart={() => startAutoScroll('down')}
          onHoverEnd={stopAutoScroll}
          onJump={scrollToEnd}
        />
      )}
    </div>
  )
}

/**
 * An individual option inside `ComboboxList`. Shows a checkmark for the
 * selected item in single-select mode, and a checkbox in multi-select mode.
 * Pass an `Icon` to display an icon to the left of the label.
 */
function ComboboxItem({
  className,
  children,
  Icon,
  multiple: multipleProp,
  ...props
}: ComboboxPrimitive.Item.Props & {
  /** Optional icon rendered to the left of the label. */
  Icon?: React.ReactElement
  /** Override `multiple` mode for this item. Defaults to the value provided by `Combobox`. */
  multiple?: boolean
}) {
  const multipleFromContext = useContext(ComboboxMultipleContext)
  const multiple = multipleProp ?? multipleFromContext
  const size = useContext(ComboboxSizeContext)
  const resolvedSize = resolveComboboxSize(size)

  return (
    <ComboboxPrimitive.Item
      data-slot="combobox-item"
      className={cn(
        multiple && 'group/item',
        getDropdownItemBaseClassName(resolvedSize),
        'text-muted-foreground',
        'data-[highlighted]:bg-accent',
        'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
        '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:text-muted-foreground',
        '[&_img]:shrink-0',
        multiple
          ? '[&[data-selected]>span:last-child]:!text-brand'
          : 'data-[selected]:!text-brand [&[data-selected]_span]:!text-brand [&[data-selected]_svg]:!text-brand',
        className,
      )}
      style={{
        boxSizing: 'border-box',
        height: DROPDOWN_ELEMENT_HEIGHT[resolvedSize],
        minHeight: DROPDOWN_ELEMENT_HEIGHT[resolvedSize],
      }}
      {...props}
    >
      {multiple ? (
        <span
          className={cn(
            'pointer-events-none absolute flex size-4 items-center justify-center rounded-sm border border-input-border opacity-50 group-data-[selected]/item:border-link group-data-[selected]/item:bg-link group-data-[selected]/item:opacity-100 [&_svg]:invisible group-data-[selected]/item:[&_svg]:visible',
            getDropdownItemIndicatorOffsetClassName(resolvedSize),
          )}
        >
          <SvgCheck className="size-2 !text-primary-foreground" />
        </span>
      ) : (
        <span
          className={cn(
            'pointer-events-none absolute flex size-4 items-center justify-center',
            getDropdownItemIndicatorOffsetClassName(resolvedSize),
          )}
        >
          <ComboboxPrimitive.ItemIndicator>
            <SvgCheck color="brand" className="size-4" />
          </ComboboxPrimitive.ItemIndicator>
        </span>
      )}
      {Icon && Icon}
      <span className="flex items-center gap-2 truncate pr-6">{children}</span>
    </ComboboxPrimitive.Item>
  )
}

/** Groups related items inside `ComboboxContent`. Pair with `ComboboxLabel` to add a visible heading above the group. */
function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
  return (
    <ComboboxPrimitive.Group
      data-slot="combobox-group"
      className={cn(className)}
      {...props}
    />
  )
}

/** Non-interactive heading label for a `ComboboxGroup`. Renders as small muted text above the group's items. */
function ComboboxLabel({
  className,
  ...props
}: ComboboxPrimitive.GroupLabel.Props) {
  const listSize = useContext(ComboboxSizeContext)
  return (
    <ComboboxPrimitive.GroupLabel
      data-slot="combobox-label"
      className={cn(
        'px-4 py-1.5 text-input-muted-foreground',
        getInputControlTextClassName(listSize),
        className,
      )}
      {...props}
    />
  )
}

/** Empty-state message shown when the filtered list has no matching items. Hidden when results are present. */
function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
  const size = useContext(ComboboxSizeContext)
  return (
    <ComboboxPrimitive.Empty
      data-slot="combobox-empty"
      className={cn(
        'hidden w-full items-center justify-center py-2 text-center text-input-muted-foreground',
        getInputControlTextClassName(size),
        'group-data-[empty]/combobox-content:flex',
        className,
      )}
      style={{ minHeight: DROPDOWN_EMPTY_STATE_HEIGHT[size] }}
      {...props}
    />
  )
}

/** Horizontal rule for visually separating item groups inside `ComboboxContent`. */
function ComboboxSeparator({
  className,
  ...props
}: ComboboxPrimitive.Separator.Props) {
  return (
    <ComboboxPrimitive.Separator
      data-slot="combobox-separator"
      className={cn('-mx-1 my-1 h-px bg-border', className)}
      {...props}
    />
  )
}

/**
 * A removable badge chip for multi-select values. Used automatically inside
 * `ComboboxTagsValue`. Can also be composed standalone in a custom trigger.
 * Inherits the trigger's size via context.
 */
function ComboboxBadge({
  className,
  children,
  ...props
}: Omit<
  React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Chip>,
  'render'
>) {
  const chipSize = useContext(ComboboxTriggerSizeContext)
  return (
    <ComboboxPrimitive.Chip
      data-slot="combobox-badge"
      tabIndex={-1}
      className={cn(
        'inline-flex shrink-0 items-center gap-2 whitespace-nowrap rounded-md border border-border bg-background-alt !text-xs font-normal !leading-4 text-pill-foreground transition-colors',
        '[&_span]:!text-xs [&_span]:!leading-4',
        chipSize === 'default' && 'h-8 px-2 py-2',
        chipSize === 'sm' && 'h-6 px-2 py-2',
        chipSize === 'xs' && 'h-6 px-1 py-1',
        className,
      )}
      {...props}
    >
      <span
        className={cn(
          'flex min-w-0 items-center gap-1',
          (chipSize === 'default' || chipSize === 'sm') &&
            '[&_img]:size-4 [&_svg]:size-4',
          chipSize === 'xs' && '[&_img]:size-3 [&_svg]:size-3',
        )}
      >
        {children}
      </span>
      <ComboboxPrimitive.ChipRemove
        data-slot="combobox-badge-remove"
        className="flex size-3 shrink-0 items-center justify-center text-muted-more-foreground data-[disabled]:text-input-muted-more-foreground"
        render={(props: React.HTMLAttributes<HTMLSpanElement>) => (
          <span
            {...props}
            role="button"
            tabIndex={-1}
            onMouseDown={(e) => {
              e.stopPropagation()
              props.onMouseDown?.(e)
            }}
            onClick={(e) => {
              e.stopPropagation()
              props.onClick?.(e)
            }}
          />
        )}
      >
        <SvgDelete1
          color="currentColor"
          className="size-2 [&_path]:stroke-[1]"
        />
      </ComboboxPrimitive.ChipRemove>
    </ComboboxPrimitive.Chip>
  )
}

/**
 * Multi-select value display with horizontally-scrollable badges and
 * `maxCount` truncation: when selected count > maxCount, shows first maxCount
 * chips plus a "+N more" chip. Clicking the chip clears the overflow (keeps
 * first maxCount) using the root's value setter — no callback needed.
 * Not a Base UI prop — Blocks addition.
 */
function ComboboxTagsValue<T>({
  placeholder,
  maxCount = 3,
  children,
  className,
}: {
  placeholder?: string
  maxCount?: number
  children: (item: T) => React.ReactNode
  className?: string
}) {
  const setValue = useContext(ComboboxSetValueContext)
  const chipSize = useContext(ComboboxTriggerSizeContext)
  const disabled = useContext(ComboboxDisabledContext)

  return (
    <ComboboxPrimitive.Value>
      {(rawValue: any) => {
        const values: T[] = Array.isArray(rawValue) ? rawValue : []

        if (values.length === 0) {
          return (
            <span
              data-slot="combobox-value"
              data-placeholder="true"
              className={cn(
                'flex flex-row items-center gap-2 truncate text-input-muted-foreground',
                disabled && '!text-input-muted-more-foreground',
                className,
              )}
            >
              {placeholder}
            </span>
          )
        }

        const visibleItems = values.slice(0, maxCount)
        const extraCount = Math.max(0, values.length - maxCount)
        const handleClearOverflow = () => {
          setValue?.(values.slice(0, maxCount))
        }

        return (
          <ComboboxPrimitive.Chips
            data-slot="combobox-tags"
            tabIndex={-1}
            className={cn(
              'flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto overflow-y-hidden py-0.5',
              '[scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
              className,
            )}
            {...(disabled
              ? {}
              : {
                  onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) =>
                    e.currentTarget.scrollTo({
                      left: e.currentTarget.scrollWidth,
                      behavior: 'smooth',
                    }),
                  onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) =>
                    e.currentTarget.scrollTo({ left: 0, behavior: 'smooth' }),
                })}
          >
            {visibleItems.map((item, index) => (
              <ComboboxBadge key={index}>{children(item)}</ComboboxBadge>
            ))}
            {extraCount > 0 && (
              <span
                role="button"
                tabIndex={0}
                data-slot="combobox-badge"
                className={cn(
                  'inline-flex shrink-0 items-center gap-2 whitespace-nowrap rounded-md border border-border bg-background-alt !text-xs font-normal !leading-4 text-pill-foreground',
                  '[&_span]:!text-xs [&_span]:!leading-4',
                  chipSize === 'default' && 'h-8 px-2 py-2',
                  chipSize === 'sm' && 'h-6 px-2 py-2',
                  chipSize === 'xs' && 'h-6 px-1 py-1',
                  'cursor-pointer hover:bg-background-alt/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring',
                )}
                onClick={(e) => {
                  e.stopPropagation()
                  e.preventDefault()
                  handleClearOverflow()
                }}
                onKeyDown={(e) => {
                  if (e.key === 'Enter' || e.key === ' ') {
                    e.preventDefault()
                    e.stopPropagation()
                    handleClearOverflow()
                  }
                }}
                onMouseDown={(e) => e.stopPropagation()}
                aria-label={`Clear overflow, keep first ${maxCount} selected`}
              >
                {`+${extraCount} more`}
                <span
                  className="flex size-3 shrink-0 items-center justify-center text-muted-more-foreground"
                  aria-hidden
                >
                  <SvgDelete1
                    color="currentColor"
                    className="size-2 [&_path]:stroke-[1]"
                  />
                </span>
              </span>
            )}
          </ComboboxPrimitive.Chips>
        )
      }}
    </ComboboxPrimitive.Value>
  )
}

/**
 * A synthetic "Select All" item for multi-select mode. Rendered automatically by
 * `ComboboxList` when `multiple` — use directly only to customise `label`.
 * Checked when all available items are selected. Hidden when the list is empty.
 */
function ComboboxSelectAll({
  checked,
  onToggle,
  label = 'Select All',
  className,
}: {
  checked: boolean
  onToggle: () => void
  label?: string
  className?: string
}) {
  const size = useContext(ComboboxSizeContext)
  const resolvedSize = resolveComboboxSize(size)

  return (
    <ComboboxPrimitive.Item
      value={COMBOBOX_SELECT_ALL_VALUE}
      aria-selected={checked}
      data-slot="combobox-select-all"
      className={cn(
        getDropdownItemBaseClassName(resolvedSize),
        'text-muted-foreground data-[highlighted]:bg-accent',
        'group-data-[empty]/combobox-content:hidden',
        className,
      )}
      style={{
        boxSizing: 'border-box',
        height: DROPDOWN_ELEMENT_HEIGHT[resolvedSize],
        minHeight: DROPDOWN_ELEMENT_HEIGHT[resolvedSize],
      }}
      onClick={(event: BaseUiMouseEvent) => {
        event.preventBaseUIHandler()
        onToggle()
      }}
    >
      <span className={cn('truncate pr-6', checked && 'text-brand')}>
        {label}
      </span>
      <span
        className={cn(
          'pointer-events-none absolute flex size-4 items-center justify-center rounded-sm border',
          getDropdownItemIndicatorOffsetClassName(resolvedSize),
          checked
            ? 'border-link bg-link text-primary-foreground'
            : 'border-input-border text-primary-foreground opacity-50 [&_svg]:invisible',
        )}
      >
        <SvgCheck className="size-2 !text-primary-foreground" />
      </span>
    </ComboboxPrimitive.Item>
  )
}

export {
  Combobox,
  ComboboxTrigger,
  ComboboxValue,
  ComboboxTagsValue,
  ComboboxClear,
  ComboboxContent,
  ComboboxInput,
  ComboboxList,
  ComboboxItem,
  ComboboxGroup,
  ComboboxLabel,
  ComboboxEmpty,
  ComboboxSelectAll,
  ComboboxSeparator,
  ComboboxBadge,
}
