import {
  computed,
  defineComponent,
  onBeforeUnmount,
  onMounted,
  provide,
  ref,
  shallowRef,
  toRefs,
  watch,
  watchEffect,
} from 'vue'
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'

import type {
  FocusEventHandler,
  KeyboardEventHandler,
  MouseEventHandler,
} from '../_util/EventInterface'
import KeyCode from '../_util/KeyCode'
import classNames from '../_util/classNames'
import createRef from '../_util/createRef'
import { initDefaultProps, isValidElement } from '../_util/props-util'
import { toReactive } from '../_util/toReactive'
import type { Key, VueNode } from '../_util/type'
import { cloneElement } from '../_util/vnode'
import PropTypes from '../_util/vue-types'
import useInjectLegacySelectContext from '../vc-tree-select/LegacyContext'
import type { AlignType } from '../vc-trigger/interface'
import isMobile from '../vc-util/isMobile'
import type { ScrollConfig, ScrollTo } from '../vc-virtual-list/List'
import type { BaseOptionType } from './Select'
import type { RefTriggerProps } from './SelectTrigger'
import SelectTrigger from './SelectTrigger'
import type { RefSelectorProps } from './Selector'
import Selector from './Selector'
import TransBtn from './TransBtn'
import type { BaseSelectContextProps } from './hooks/useBaseProps'
import { useProvideBaseSelectProps } from './hooks/useBaseProps'
import useDelayReset from './hooks/useDelayReset'
import useLock from './hooks/useLock'
import useSelectTriggerControl from './hooks/useSelectTriggerControl'
import { getSeparatedContent } from './utils/valueUtil'

const DEFAULT_OMIT_PROPS = [
  'value',
  'onChange',
  'removeIcon',
  'placeholder',
  'autofocus',
  'maxTagCount',
  'maxTagTextLength',
  'maxTagPlaceholder',
  'choiceTransitionName',
  'onInputKeyDown',
  'onPopupScroll',
  'tabindex',
  'OptionList',
  'notFoundContent',
] as const

export type RenderNode = VueNode | ((props: any) => VueNode)

export type RenderDOMFunc = (props: any) => HTMLElement

export type Mode = 'multiple' | 'tags' | 'combobox'

export type Placement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'

export type RawValueType = string | number

export interface RefOptionListProps {
  onKeydown: KeyboardEventHandler
  onKeyup: KeyboardEventHandler
  scrollTo?: (index: number | ScrollConfig) => void
}

export interface CustomTagProps {
  label: any
  value: any
  disabled: boolean
  onClose: (event?: MouseEvent) => void
  closable: boolean
  option: BaseOptionType
}

export interface DisplayValueType {
  key?: Key
  value?: RawValueType
  label?: any
  disabled?: boolean
  option?: BaseOptionType
}

export interface BaseSelectRef {
  focus: () => void
  blur: () => void
  scrollTo: ScrollTo
}

const baseSelectPrivateProps = () => {
  return {
    prefixCls: String,
    id: String,
    omitDomProps: Array as PropType<string[]>,

    // >>> Value
    displayValues: Array as PropType<DisplayValueType[]>,
    onDisplayValuesChange: Function as PropType<
      (
        values: DisplayValueType[],
        info: {
          type: 'add' | 'remove' | 'clear'
          values: DisplayValueType[]
        },
      ) => void
    >,

    // >>> Active
    /** Current dropdown list active item string value */
    activeValue: String,
    /** Link search input with target element */
    activeDescendantId: String,
    onActiveValueChange: Function as PropType<(value: string | null) => void>,

    // >>> Search
    searchValue: String,
    /** Trigger onSearch, return false to prevent trigger open event */
    onSearch: Function as PropType<
      (
        searchValue: string,
        info: {
          source:
            | 'typing' //User typing
            | 'effect' // Code logic trigger
            | 'submit' // tag mode only
            | 'blur' // Not trigger event
        },
      ) => void
    >,
    /** Trigger when search text match the `tokenSeparators`. Will provide split content */
    onSearchSplit: Function as PropType<(words: string[]) => void>,
    maxLength: Number,

    OptionList: PropTypes.any,

    /** Tell if provided `options` is empty */
    emptyOptions: Boolean,
  }
}

export interface DropdownObject {
  menuNode?: VueNode
  props?: Record<string, any>
}

export type DropdownRender = (opt?: DropdownObject) => VueNode
export const baseSelectPropsWithoutPrivate = () => {
  return {
    showSearch: { type: Boolean, default: undefined },
    tagRender: { type: Function as PropType<(props: CustomTagProps) => any> },
    optionLabelRender: { type: Function as PropType<(option: Record<string, any>) => any> },
    direction: { type: String as PropType<'ltr' | 'rtl'> },

    // MISC
    tabindex: Number,
    autofocus: Boolean,
    notFoundContent: PropTypes.any,
    placeholder: PropTypes.any,
    onClear: Function as PropType<() => void>,

    choiceTransitionName: String,

    // >>> Mode
    mode: String as PropType<Mode>,

    // >>> Status
    disabled: { type: Boolean, default: undefined },
    loading: { type: Boolean, default: undefined },

    // >>> Open
    open: { type: Boolean, default: undefined },
    defaultOpen: { type: Boolean, default: undefined },
    onDropdownVisibleChange: { type: Function as PropType<(open: boolean) => void> },

    // >>> Customize Input
    getInputElement: { type: Function as PropType<() => any> },
    getRawInputElement: { type: Function as PropType<() => any> },

    // >>> Selector
    maxTagTextLength: Number,
    maxTagCount: { type: [String, Number] as PropType<number | 'responsive'> },
    maxTagPlaceholder: PropTypes.any,

    // >>> Search
    tokenSeparators: { type: Array as PropType<string[]> },

    // >>> Icons
    allowClear: { type: Boolean, default: undefined },
    showArrow: { type: Boolean, default: undefined },
    inputIcon: PropTypes.any,
    /** Clear all icon */
    clearIcon: PropTypes.any,
    /** Selector remove icon */
    removeIcon: PropTypes.any,

    // >>> Dropdown
    animation: String,
    transitionName: String,
    dropdownStyle: { type: Object as PropType<CSSProperties> },
    dropdownClassName: String,
    dropdownMatchSelectWidth: {
      type: [Boolean, Number] as PropType<boolean | number>,
      default: undefined,
    },
    dropdownRender: { type: Function as PropType<DropdownRender> },
    dropdownAlign: Object as PropType<AlignType>,
    placement: {
      type: String as PropType<Placement>,
    },
    getPopupContainer: { type: Function as PropType<RenderDOMFunc> },

    // >>> Focus
    showAction: { type: Array as PropType<('focus' | 'click')[]> },
    onBlur: { type: Function as PropType<(e: FocusEvent) => void> },
    onFocus: { type: Function as PropType<(e: FocusEvent) => void> },

    // >>> Rest Events
    onKeyup: Function as PropType<(e: KeyboardEvent) => void>,
    onKeydown: Function as PropType<(e: KeyboardEvent) => void>,
    onMousedown: Function as PropType<(e: MouseEvent) => void>,
    onPopupScroll: Function as PropType<(e: UIEvent) => void>,
    onInputKeyDown: Function as PropType<(e: KeyboardEvent) => void>,
    onMouseenter: Function as PropType<(e: MouseEvent) => void>,
    onMouseleave: Function as PropType<(e: MouseEvent) => void>,
    onClick: Function as PropType<(e: MouseEvent) => void>,
  }
}
const baseSelectProps = () => {
  return {
    ...baseSelectPrivateProps(),
    ...baseSelectPropsWithoutPrivate(),
  }
}

export type BaseSelectPrivateProps = Partial<
  ExtractPropTypes<ReturnType<typeof baseSelectPrivateProps>>
>

export type BaseSelectProps = Partial<ExtractPropTypes<ReturnType<typeof baseSelectProps>>>

export type BaseSelectPropsWithoutPrivate = Omit<BaseSelectProps, keyof BaseSelectPrivateProps>

export function isMultiple(mode: Mode) {
  return mode === 'tags' || mode === 'multiple'
}

export default defineComponent({
  compatConfig: { MODE: 3 },
  name: 'BaseSelect',
  inheritAttrs: false,
  props: initDefaultProps(baseSelectProps(), { showAction: [], notFoundContent: 'Not Found' }),
  setup(props, { attrs, expose, slots }) {
    const multiple = computed(() => isMultiple(props.mode))

    const mergedShowSearch = computed(() =>
      props.showSearch !== undefined
        ? props.showSearch
        : multiple.value || props.mode === 'combobox',
    )
    const mobile = shallowRef(false)
    onMounted(() => {
      mobile.value = isMobile()
    })
    const legacyTreeSelectContext = useInjectLegacySelectContext()
    // ============================== Refs ==============================
    const containerRef = shallowRef<HTMLDivElement>(null)
    const selectorDomRef = createRef()
    const triggerRef = shallowRef<RefTriggerProps>(null)
    const selectorRef = shallowRef<RefSelectorProps>(null)
    const listRef = shallowRef<RefOptionListProps>(null)
    const blurRef = ref<boolean>(false)

    /** Used for component focused management */
    const [mockFocused, setMockFocused, cancelSetMockFocused] = useDelayReset()

    const focus = () => {
      selectorRef.value?.focus()
    }
    const blur = () => {
      selectorRef.value?.blur()
    }
    expose({
      focus,
      blur,
      scrollTo: (arg) => listRef.value?.scrollTo(arg),
    })

    const mergedSearchValue = computed(() => {
      if (props.mode !== 'combobox') {
        return props.searchValue
      }

      const val = props.displayValues[0]?.value

      return typeof val === 'string' || typeof val === 'number' ? String(val) : ''
    })

    // ============================== Open ==============================
    const initOpen = props.open !== undefined ? props.open : props.defaultOpen
    const innerOpen = shallowRef(initOpen)
    const mergedOpen = shallowRef(initOpen)
    const setInnerOpen = (val: boolean) => {
      innerOpen.value = props.open !== undefined ? props.open : val
      mergedOpen.value = innerOpen.value
    }
    watch(
      () => props.open,
      () => {
        setInnerOpen(props.open)
      },
    )

    // Not trigger `open` in `combobox` when `notFoundContent` is empty
    const emptyListContent = computed(() => !props.notFoundContent && props.emptyOptions)

    watchEffect(() => {
      mergedOpen.value = innerOpen.value
      if (
        props.disabled ||
        (emptyListContent.value && mergedOpen.value && props.mode === 'combobox')
      ) {
        mergedOpen.value = false
      }
    })

    const triggerOpen = computed(() => (emptyListContent.value ? false : mergedOpen.value))

    const onToggleOpen = (newOpen?: boolean) => {
      const nextOpen = newOpen !== undefined ? newOpen : !mergedOpen.value

      if (mergedOpen.value !== nextOpen && !props.disabled) {
        setInnerOpen(nextOpen)
        props.onDropdownVisibleChange && props.onDropdownVisibleChange(nextOpen)

        if (!nextOpen && popupFocused.value) {
          popupFocused.value = false
          setMockFocused(false, () => {
            focusRef.value = false
            blurRef.value = false
          })
        }
      }
    }

    const tokenWithEnter = computed(() =>
      (props.tokenSeparators || []).some((tokenSeparator) =>
        ['\n', '\r\n'].includes(tokenSeparator),
      ),
    )

    const onInternalSearch = (searchText: string, fromTyping: boolean, isCompositing: boolean) => {
      let ret = true
      let newSearchText = searchText
      props.onActiveValueChange?.(null)

      // Check if match the `tokenSeparators`
      const patchLabels: string[] = isCompositing
        ? null
        : getSeparatedContent(searchText, props.tokenSeparators)

      // Ignore combobox since it's not split-able
      if (props.mode !== 'combobox' && patchLabels) {
        newSearchText = ''

        props.onSearchSplit?.(patchLabels)

        // Should close when paste finish
        onToggleOpen(false)

        // Tell Selector that break next actions
        ret = false
      }

      if (props.onSearch && mergedSearchValue.value !== newSearchText) {
        props.onSearch(newSearchText, {
          source: fromTyping ? 'typing' : 'effect',
        })
      }

      return ret
    }

    // Only triggered when menu is closed & mode is tags
    // If menu is open, OptionList will take charge
    // If mode isn't tags, press enter is not meaningful when you can't see any option
    const onInternalSearchSubmit = (searchText: string) => {
      // prevent empty tags from appearing when you click the Enter button
      if (!searchText || !searchText.trim()) {
        return
      }
      props.onSearch?.(searchText, { source: 'submit' })
    }

    // Close will clean up single mode search text
    watch(
      mergedOpen,
      () => {
        if (!mergedOpen.value && !multiple.value && props.mode !== 'combobox') {
          onInternalSearch('', false, false)
        }
      },
      { immediate: true, flush: 'post' },
    )

    // ============================ Disabled ============================
    // Close dropdown & remove focus state when disabled change
    watch(
      () => props.disabled,
      () => {
        if (innerOpen.value && !!props.disabled) {
          setInnerOpen(false)
        }
        if (props.disabled && !blurRef.value) {
          setMockFocused(false)
        }
      },
      { immediate: true },
    )

    // ============================ Keyboard ============================
    /**
     * We record input value here to check if can press to clean up by backspace
     * - null: Key is not down, this is reset by key up
     * - true: Search text is empty when first time backspace down
     * - false: Search text is not empty when first time backspace down
     */
    const [getClearLock, setClearLock] = useLock()

    // KeyDown
    const onInternalKeyDown: KeyboardEventHandler = (event, ...rest) => {
      const clearLock = getClearLock()
      const { which } = event

      if (which === KeyCode.ENTER) {
        // Do not submit form when type in the input
        if (props.mode !== 'combobox') {
          event.preventDefault()
        }

        // We only manage open state here, close logic should handle by list component
        if (!mergedOpen.value) {
          onToggleOpen(true)
        }
      }

      setClearLock(!!mergedSearchValue.value)

      // Remove value by `backspace`
      if (
        which === KeyCode.BACKSPACE &&
        !clearLock &&
        multiple.value &&
        !mergedSearchValue.value &&
        props.displayValues.length
      ) {
        const cloneDisplayValues = [...props.displayValues]
        let removedDisplayValue = null

        for (let i = cloneDisplayValues.length - 1; i >= 0; i -= 1) {
          const current = cloneDisplayValues[i]

          if (!current.disabled) {
            cloneDisplayValues.splice(i, 1)
            removedDisplayValue = current
            break
          }
        }

        if (removedDisplayValue) {
          props.onDisplayValuesChange(cloneDisplayValues, {
            type: 'remove',
            values: [removedDisplayValue],
          })
        }
      }

      if (mergedOpen.value && listRef.value) {
        listRef.value.onKeydown(event, ...rest)
      }

      props.onKeydown?.(event, ...rest)
    }

    // KeyUp
    const onInternalKeyUp: KeyboardEventHandler = (event: KeyboardEvent, ...rest) => {
      if (mergedOpen.value && listRef.value) {
        listRef.value.onKeyup(event, ...rest)
      }

      if (props.onKeyup) {
        props.onKeyup(event, ...rest)
      }
    }

    // ============================ Selector ============================
    const onSelectorRemove = (val: DisplayValueType) => {
      const newValues = props.displayValues.filter((i) => i !== val)

      props.onDisplayValuesChange(newValues, {
        type: 'remove',
        values: [val],
      })
    }

    // ========================== Focus / Blur ==========================
    /** Record real focus status */
    const focusRef = shallowRef(false)
    const onContainerFocus: FocusEventHandler = (...args) => {
      setMockFocused(true)

      if (!props.disabled) {
        if (props.onFocus && !focusRef.value) {
          props.onFocus(...args)
        }

        // `showAction` should handle `focus` if set
        if (props.showAction && props.showAction.includes('focus')) {
          onToggleOpen(true)
        }
      }

      focusRef.value = true
    }
    const popupFocused = ref(false)
    const onContainerBlur: FocusEventHandler = (...args) => {
      if (popupFocused.value) {
        return
      }
      blurRef.value = true
      setMockFocused(false, () => {
        focusRef.value = false
        blurRef.value = false
        onToggleOpen(false)
      })

      if (props.disabled) {
        return
      }
      const searchVal = mergedSearchValue.value
      if (searchVal) {
        // `tags` mode should move `searchValue` into values
        if (props.mode === 'tags') {
          props.onSearch(searchVal, { source: 'submit' })
        } else if (props.mode === 'multiple') {
          // `multiple` mode only clean the search value but not trigger event
          props.onSearch('', {
            source: 'blur',
          })
        }
      }

      if (props.onBlur) {
        props.onBlur(...args)
      }
    }
    const onPopupFocusin = () => {
      popupFocused.value = true
    }
    const onPopupFocusout = () => {
      popupFocused.value = false
    }
    provide('VCSelectContainerEvent', {
      focus: onContainerFocus,
      blur: onContainerBlur,
    })

    // Give focus back of Select
    const activeTimeoutIds: any[] = []

    onMounted(() => {
      activeTimeoutIds.forEach((timeoutId) => clearTimeout(timeoutId))
      activeTimeoutIds.splice(0, activeTimeoutIds.length)
    })
    onBeforeUnmount(() => {
      activeTimeoutIds.forEach((timeoutId) => clearTimeout(timeoutId))
      activeTimeoutIds.splice(0, activeTimeoutIds.length)
    })

    const onInternalMouseDown: MouseEventHandler = (event, ...restArgs) => {
      const { target } = event
      const popupElement: HTMLDivElement = triggerRef.value?.getPopupElement()

      // We should give focus back to selector if clicked item is not focusable
      if (popupElement && popupElement.contains(target as HTMLElement)) {
        const timeoutId: any = setTimeout(() => {
          const index = activeTimeoutIds.indexOf(timeoutId)
          if (index !== -1) {
            activeTimeoutIds.splice(index, 1)
          }

          cancelSetMockFocused()

          if (!mobile.value && !popupElement.contains(document.activeElement)) {
            selectorRef.value?.focus()
          }
        })

        activeTimeoutIds.push(timeoutId)
      }

      props.onMousedown?.(event, ...restArgs)
    }

    // ============================= Dropdown ==============================
    const containerWidth = shallowRef<number>(null)
    // const instance = getCurrentInstance();
    const onPopupMouseEnter = () => {
      // We need force update here since popup dom is render async
      // instance.update();
    }
    onMounted(() => {
      watch(
        triggerOpen,
        () => {
          if (triggerOpen.value) {
            const newWidth = Math.ceil(containerRef.value?.offsetWidth)
            if (containerWidth.value !== newWidth && !Number.isNaN(newWidth)) {
              containerWidth.value = newWidth
            }
          }
        },
        { immediate: true, flush: 'post' },
      )
    })

    // Close when click on non-select element
    useSelectTriggerControl([containerRef, triggerRef], triggerOpen, onToggleOpen)
    useProvideBaseSelectProps(
      toReactive({
        ...toRefs(props),
        open: mergedOpen,
        triggerOpen,
        showSearch: mergedShowSearch,
        multiple,
        toggleOpen: onToggleOpen,
      } as unknown as BaseSelectContextProps),
    )
    return () => {
      const {
        prefixCls,
        id,

        open,
        defaultOpen,

        mode,

        // Search related
        showSearch,
        searchValue,
        onSearch,

        // Icons
        allowClear,
        clearIcon,
        showArrow,
        inputIcon,

        // Others
        disabled,
        loading,
        getInputElement,
        getPopupContainer,
        placement,

        // Dropdown
        animation,
        transitionName,
        dropdownStyle,
        dropdownClassName,
        dropdownMatchSelectWidth,
        dropdownRender,
        dropdownAlign,
        showAction,
        direction,

        // Tags
        tokenSeparators,
        tagRender,
        optionLabelRender,

        // Events
        onPopupScroll,
        onDropdownVisibleChange,
        onFocus,
        onBlur,
        onKeyup,
        onKeydown,
        onMousedown,

        onClear,
        omitDomProps,
        getRawInputElement,
        displayValues,
        onDisplayValuesChange,
        emptyOptions,
        activeDescendantId,
        activeValue,
        OptionList,

        ...restProps
      } = { ...props, ...attrs } as BaseSelectProps
      // ============================= Input ==============================
      // Only works in `combobox`
      const customizeInputElement: any =
        (mode === 'combobox' && getInputElement && getInputElement()) || null

      // Used for customize replacement for `vc-cascader`
      const customizeRawInputElement: any =
        typeof getRawInputElement === 'function' && getRawInputElement()
      const domProps = {
        ...restProps,
      } as Omit<keyof typeof restProps, (typeof DEFAULT_OMIT_PROPS)[number]>

      // Used for raw custom input trigger
      let onTriggerVisibleChange: null | ((newOpen: boolean) => void)
      if (customizeRawInputElement) {
        onTriggerVisibleChange = (newOpen: boolean) => {
          onToggleOpen(newOpen)
        }
      }

      DEFAULT_OMIT_PROPS.forEach((propName) => {
        delete domProps[propName]
      })

      omitDomProps?.forEach((propName) => {
        delete domProps[propName]
      })

      // ============================= Arrow ==============================
      const mergedShowArrow =
        showArrow !== undefined ? showArrow : loading || (!multiple.value && mode !== 'combobox')
      let arrowNode: VueNode

      if (mergedShowArrow) {
        arrowNode = (
          <TransBtn
            class={classNames(`${prefixCls}-arrow`, {
              [`${prefixCls}-arrow-loading`]: loading,
            })}
            customizeIcon={inputIcon}
            customizeIconProps={{
              loading,
              searchValue: mergedSearchValue.value,
              open: mergedOpen.value,
              focused: mockFocused.value,
              showSearch: mergedShowSearch.value,
            }}
          />
        )
      }

      // ============================= Clear ==============================
      let clearNode: VueNode
      const onClearMouseDown: MouseEventHandler = () => {
        onClear?.()

        onDisplayValuesChange([], {
          type: 'clear',
          values: displayValues,
        })
        onInternalSearch('', false, false)
      }

      if (!disabled && allowClear && (displayValues.length || mergedSearchValue.value)) {
        clearNode = (
          <TransBtn
            class={`${prefixCls}-clear`}
            onMousedown={onClearMouseDown}
            customizeIcon={clearIcon}
          >
            ×
          </TransBtn>
        )
      }

      // =========================== OptionList ===========================
      const optionList = (
        <OptionList
          ref={listRef}
          v-slots={{ ...legacyTreeSelectContext.customSlots, option: slots.option }}
        />
      )

      // ============================= Select =============================
      const mergedClassName = classNames(prefixCls, attrs.class, {
        [`${prefixCls}-focused`]: mockFocused.value,
        [`${prefixCls}-multiple`]: multiple.value,
        [`${prefixCls}-single`]: !multiple.value,
        [`${prefixCls}-allow-clear`]: allowClear,
        [`${prefixCls}-show-arrow`]: mergedShowArrow,
        [`${prefixCls}-disabled`]: disabled,
        [`${prefixCls}-loading`]: loading,
        [`${prefixCls}-open`]: mergedOpen.value,
        [`${prefixCls}-customize-input`]: customizeInputElement,
        [`${prefixCls}-show-search`]: mergedShowSearch.value,
      })

      // >>> Selector
      const selectorNode = (
        <SelectTrigger
          ref={triggerRef}
          disabled={disabled}
          prefixCls={prefixCls}
          visible={triggerOpen.value}
          popupElement={optionList}
          containerWidth={containerWidth.value}
          animation={animation}
          transitionName={transitionName}
          dropdownStyle={dropdownStyle}
          dropdownClassName={dropdownClassName}
          direction={direction}
          dropdownMatchSelectWidth={dropdownMatchSelectWidth}
          dropdownRender={dropdownRender}
          dropdownAlign={dropdownAlign}
          placement={placement}
          getPopupContainer={getPopupContainer}
          empty={emptyOptions}
          getTriggerDOMNode={() => selectorDomRef.current}
          onPopupVisibleChange={onTriggerVisibleChange}
          onPopupMouseEnter={onPopupMouseEnter}
          onPopupFocusin={onPopupFocusin}
          onPopupFocusout={onPopupFocusout}
          v-slots={{
            default: () => {
              return customizeRawInputElement ? (
                isValidElement(customizeRawInputElement) &&
                  cloneElement(
                    customizeRawInputElement,
                    {
                      ref: selectorDomRef,
                      tabindex: 0,
                    },
                    false,
                    true,
                  )
              ) : (
                <Selector
                  {...props}
                  domRef={selectorDomRef}
                  prefixCls={prefixCls}
                  inputElement={customizeInputElement}
                  ref={selectorRef}
                  id={id}
                  showSearch={mergedShowSearch.value}
                  mode={mode}
                  activeDescendantId={activeDescendantId}
                  tagRender={tagRender}
                  optionLabelRender={optionLabelRender}
                  values={displayValues}
                  open={mergedOpen.value}
                  onToggleOpen={onToggleOpen}
                  activeValue={activeValue}
                  searchValue={mergedSearchValue.value}
                  onSearch={onInternalSearch}
                  onSearchSubmit={onInternalSearchSubmit}
                  onRemove={onSelectorRemove}
                  tokenWithEnter={tokenWithEnter.value}
                />
              )
            },
          }}
        ></SelectTrigger>
      )
      // >>> Render
      let renderNode: VueNode

      // Render raw
      if (customizeRawInputElement) {
        renderNode = (
          <div onKeydown={onInternalKeyDown} onKeyup={onInternalKeyUp}>
            {selectorNode}
          </div>
        )
      } else {
        renderNode = (
          <div
            {...domProps}
            class={mergedClassName}
            ref={containerRef}
            onMousedown={onInternalMouseDown}
            onKeydown={onInternalKeyDown}
            onKeyup={onInternalKeyUp}
            // onFocus={onContainerFocus}
            // onBlur={onContainerBlur}
          >
            {mockFocused.value && !mergedOpen.value && (
              <span
                style={{
                  width: 0,
                  height: 0,
                  position: 'absolute',
                  overflow: 'hidden',
                  opacity: 0,
                }}
                aria-live="polite"
              >
                {/* Merge into one string to make screen reader work as expect */}
                {`${displayValues
                  .map(({ label, value }) =>
                    ['number', 'string'].includes(typeof label) ? label : value,
                  )
                  .join(', ')}`}
              </span>
            )}
            {selectorNode}

            {arrowNode}
            {clearNode}
          </div>
        )
      }
      return renderNode
    }
  },
})
