import React, {
  Fragment,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react'

import Button from '../Button/Button'
import Checkbox from '../Form/Checkbox'
import DragHandle from '../DragHandle/DragHandle'
import EmptyState from '../EmptyState/EmptyState'
import { type EmptyStateProps } from '../EmptyState/EmptyState'
import { type FormLabelProps } from '../FormLabel/FormLabel'
import { hasValue, notEmpty } from '../../services/HelperServiceTyped'
import Icon from '../Icons/Icon'
import isClient from '../../services/isClient'
import Popover from '../Popover/Popover'
import { type PopoverProps } from '../Popover/Popover'
import SearchBar from '../SearchBar/SearchBar'
import { type SearchBarProps } from '../SearchBar/SearchBar'
import SectionHeader from '../Form/SectionHeader/SectionHeader'
import { useFocus } from '../../hooks/useFocus/useFocus'
import FormLabel from '../FormLabel/FormLabel'
import styles from './_multi-select.module.scss'
import { c } from '../../translations/LibraryTranslationService'
import ScrollingContainer from '../ScrollingContainer/ScrollingContainer'
import SelectLoading from '../Form/Select/SelectLoading'
import DraggableList from '../DragAndDrop/DraggableList'
import CheckboxList from '../TreeListBox/CheckboxList'
import {
  CheckboxState,
  type DateCheckState,
  debouncedSearch,
  type GroupScrollConfig,
  type IgnoreNodeHierarchyProps,
  type TableColumnConfigProps,
  toggleExpand,
  updateItemStates,
} from '../Form/NestedCheckboxHelper'

type DisabledItemsSection<DataItem> =
  | {
      /** Hide Disabled items section */
      enabled: false
    }
  | {
      /** Show Disabled items section */
      enabled: true
      /** Title displayed at the left side of the section header */
      primaryTitle?: string
      /** Title displayed at the right side of the section header */
      secondaryTitle?: string
      /** Optionally determine which key in DataItem to use for placing items in the disabled items section. */
      disabledItemsKey?: keyof DataItem
    }

type SearchProps = Omit<SearchBarProps, 'onChange'> & {
  /** Optional function that will make a callout with the search `value` passed back */
  onChange?: (value: string) => void
  /** Boolean value to indicate if search bar to be shown */
  show?: boolean
}

export type MultiSelectDataItemBase = {
  /** Required prop that will be used as a "label". This prop must remain flexible as we do not know what an api response will look like. This will force at least one field to passed into the `DataItem` generic. */
  [key: string]: unknown
  /** Optional text that needs to be shown near the actual text */
  secondaryOption?: React.ReactNode
  /** Optional flag to indicate if the option cannot be checked or unchecked */
  disabled?: boolean
}

type MultiSelectBaseProps<DataItem extends MultiSelectDataItemBase> = {
  /** Array of options displayed in the UI */
  options: DataItem[]
  /** Array of user selected options that we want selected when the component mounts. These strings should come from the strings provided in the `options` prop. */
  selectedOptions: DataItem[]
  /** Key that is used to populate the selectable text */
  labelKey: keyof DataItem
  /** Function to set the array of selected options */
  callout: (selectedList: DataItem[]) => void
  /** Optionally add FormLabel props */
  formLabelProps?: FormLabelProps
  /** Search Bar props. Can be used to override dynamically */
  searchBarProps?: Omit<SearchProps, 'value'> & {
    /** The value should be optional */
    value?: string
  }
  /** Custom text that will display if no items match the search input */
  emptyStateProps?: EmptyStateProps
  /** Optional prop to disabled the use of this component */
  disabled?: boolean
  /** Optionally show the ListLoading component when options are not readily available */
  loading?: boolean
  /** Optionally hide the Select All */
  hideSelectAll?: boolean
  /** Optionally make the selected items reorderable using the DraggableList component. */
  isReorderable?: boolean
  /** Optionally opt-out of alphaetical sorting */
  sortAlphabetically?: boolean
  /** Optionally opt-out for showing disabled items in disabled section below unselected items*/
  disabledItemsSection?: DisabledItemsSection<DataItem>
  /** Optional function to run when the MultiSelect receives focus */
  onFocus?: () => void
  /** Optional prop to add a test id to the MultiSelect for QA testing */
  qaTestId?: string
}

type MultiSelectWithOptionsProps<DataItem extends MultiSelectDataItemBase> =
  MultiSelectBaseProps<DataItem> & {
    fetchData?: never
    hasMore?: never
    searchValue?: never
    isFetching?: never
    dropdownState?: never
  }

type MultiSelectWithAPIOptionsProps<DataItem extends MultiSelectDataItemBase> =
  MultiSelectBaseProps<DataItem> & {
    /** To trigger API call initially and on pagination */
    fetchData: () => void
    /** Boolean value to indicate if pagination exists */
    hasMore: boolean
    /** Optionally used to indicate API fetch status. */
    isFetching?: boolean
    /** Callback that gives the current open / closed state of the Tippy */
    dropdownState?: (status: 'open' | 'close') => void
  }

type MultiSelectOptionsProps<DataItem extends MultiSelectDataItemBase> =
  | MultiSelectWithAPIOptionsProps<DataItem>
  | MultiSelectWithOptionsProps<DataItem>

export type ExposedProps<DataItem extends MultiSelectDataItemBase> =
  MultiSelectOptionsProps<DataItem> & {
    /** Optionally make the component "exposed" by not needing a click to view the options. */
    exposed: boolean
    /** Optionally override the maxHeight for the exposed container. */
    maxHeight?: number | string
    appendTo?: never
    selectPlaceholder?: never
    /** Option to reset list search on callout */
    resetSearch?: boolean
    nestedConfig?: never
    /** Optionally remove the border for the exposed MultiSelect. */
    removeBorder?: boolean
  }

type StandardProps<DataItem extends MultiSelectDataItemBase> =
  MultiSelectOptionsProps<DataItem> & {
    /** Useful when we want to attach this to another component making use of Tippy */
    appendTo?: 'parent' | ((element: Element) => Element)
    /** Custom placeholder text for the Select Dropdown */
    selectPlaceholder: string
    exposed?: never
    maxHeight?: never
    resetSearch?: never
    nestedConfig?: never
    removeBorder?: never
  }

type NestedPropsBase<DataItem extends MultiSelectDataItemBase> =
  MultiSelectOptionsProps<DataItem> &
    (
      | Omit<ExposedProps<DataItem>, 'nestedConfig'>
      | Omit<StandardProps<DataItem>, 'nestedConfig'>
    )

type NestedProps<DataItem extends MultiSelectDataItemBase> =
  NestedPropsBase<DataItem> & {
    /** Useful for showing nested hierarchy selection experience */
    nestedConfig: {
      /** Data to be displayed in the nested hierarchy */
      data: DataItem[]
      /** Table configuration that will handle column headers and rendering column children. */
      tableConfig: TableColumnConfigProps<DataItem>[]
      /** Array of objects to keep the track of selected or unselected options */
      itemStates: DateCheckState[]
      /** Function to set the array of selected options */
      setItemStates: React.Dispatch<React.SetStateAction<DateCheckState[]>>
      /** Object containing keys to identify the data and its parent child relationship */
      identifierKeys: {
        displayKey: string
        idKey: string
        parentKey: string
      }
      /** Optional per-group scroll and infinite-scroll pagination config */
      groupScrollConfig?: GroupScrollConfig
      /** Optional predicate to exclude items from rendering in CheckboxList without affecting the empty-state check */
      filterItems?: (item: DataItem) => boolean
    } & IgnoreNodeHierarchyProps
  }

export type MultiSelectProps<DataItem extends MultiSelectDataItemBase> =
  | ExposedProps<DataItem>
  | StandardProps<DataItem>
  | NestedProps<DataItem>

export type MultiSelectHeaders = {
  id: number
  label: string
  isChecked: boolean
}

type OrderMapType = {
  [key: string]: number
}

const MultiSelect = <DataItem extends MultiSelectDataItemBase>({
  appendTo,
  dropdownState,
  labelKey,
  selectedOptions,
  options,
  callout,
  isFetching,
  isReorderable,
  fetchData,
  selectPlaceholder,
  searchBarProps = {
    value: '',
    placeholder: c('search'),
  },
  emptyStateProps = { primaryText: c('noOptionsAvailable') },
  exposed,
  maxHeight,
  resetSearch = false,
  disabled,
  loading,
  hideSelectAll,
  hasMore,
  formLabelProps,
  sortAlphabetically = true,
  disabledItemsSection = {
    enabled: false,
  },
  nestedConfig,
  removeBorder,
  onFocus,
  qaTestId = 'multi-select',
}: MultiSelectProps<DataItem>): React.JSX.Element => {
  const [expandedItems, setExpandedItems] = useState<{
    [key: string | number]: boolean
  }>({})
  const [filteredItems, setFilteredItems] = useState<DataItem[]>(
    nestedConfig?.data ?? [],
  )
  const {
    displayKey = '',
    idKey = '',
    parentKey = '',
  } = nestedConfig?.identifierKeys ?? {}

  // Used for lookup purpose as it has better performance than native Array.findIndex()
  const optionsInitialOrder = useMemo(() => {
    const dataOptions = options.map((item) => item[labelKey] as string)
    const optionsOrderMap: OrderMapType = {}
    for (let index = 0; index < dataOptions.length; index++) {
      optionsOrderMap[dataOptions[index]] = index
    }
    return optionsOrderMap
  }, [labelKey, options])

  // We will never sort the selected list if the component is reorderable
  const shouldSortAlphabetically = !isReorderable && sortAlphabetically

  const [disabledItemsKey, setDisabledItemsKey] =
    useState<keyof DataItem>('disabled')
  useEffect(() => {
    if (
      disabledItemsSection.enabled &&
      disabledItemsSection?.disabledItemsKey &&
      disabledItemsSection.disabledItemsKey !== disabledItemsKey
    ) {
      setDisabledItemsKey(disabledItemsSection.disabledItemsKey)
    }
  }, [disabledItemsSection, setDisabledItemsKey, disabledItemsKey])

  const {
    value: searchValue,
    show: showSearch,
    placeholder: searchPlaceholder,
    onChange: onSearchChange,
    autoFocus: autoFocusSearch,
  } = searchBarProps

  const [search, setSearch] = useState(searchValue ?? '')
  const selectBoxRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    searchValue && setSearch(searchValue)
    resetSearch && setSearch('')
  }, [resetSearch, searchValue])

  const onToggleExpand = useCallback(
    (idOrIds: string | number | (string | number)[]) => {
      toggleExpand(idOrIds, setExpandedItems)
    },
    [],
  )

  useEffect(() => {
    if (nestedConfig) {
      // Call the debouncedSearch function when the search text changes
      debouncedSearch({
        query: search,
        identifierKeys: nestedConfig.identifierKeys ?? {
          displayKey: '',
          idKey: '',
          parentKey: '',
        },
        setFilteredItems: setFilteredItems,
        nestedConfigData: nestedConfig.data ?? [],
        toggleExpand: onToggleExpand,
      })
    }
  }, [
    search,
    nestedConfig,
    nestedConfig?.data,
    nestedConfig?.identifierKeys,
    onToggleExpand,
  ])

  const [selectedList, setSelectedList] = useState<DataItem[]>(() => {
    if (shouldSortAlphabetically) {
      // Sort selectedOptions alphabetically when initializing selectedList
      return selectedOptions.sort((a, b) => {
        const labelA = a[labelKey] as string
        const labelB = b[labelKey] as string
        return labelA.localeCompare(labelB)
      })
    } else {
      return selectedOptions ?? []
    }
  })

  // Update selectedList when selectedOptions changes (with stable comparison)
  useEffect(() => {
    if (shouldSortAlphabetically) {
      // Sort selectedOptions alphabetically when updating selectedList
      const sortedOptions = [...selectedOptions].sort((a, b) => {
        const labelA = a[labelKey] as string
        const labelB = b[labelKey] as string
        return labelA.localeCompare(labelB)
      })
      setSelectedList(sortedOptions)
    } else {
      setSelectedList([...selectedOptions])
    }
  }, [selectedOptions, labelKey, shouldSortAlphabetically])

  // Function to filter selected items based on search
  const filteredSelectedList = useMemo(() => {
    return selectedList.filter((item) => {
      const labelValue = item[labelKey]
      const searchResult =
        typeof labelValue === 'string' && labelValue
          ? labelValue.toLowerCase().includes(search.toLowerCase())
          : false
      return fetchData
        ? // Sometimes there are fetched options that don't exactly match or include the search text but have still been selected
          searchResult ||
            options?.find((option) => option[labelKey] === item[labelKey])
        : searchResult
    })
  }, [selectedList, search, labelKey, options, fetchData])

  const getUnselectedList = useCallback(
    (
      selectedTexts: DataItem[keyof DataItem][],
      totalItems: DataItem[],
      searchText: string,
    ) => {
      return totalItems.filter((item) => {
        const labelValue = item[labelKey]
        return (
          (disabledItemsSection?.enabled ? !item[disabledItemsKey] : true) &&
          !selectedTexts.includes(item[labelKey]) &&
          (fetchData
            ? true
            : typeof labelValue === 'string' && labelValue
              ? labelValue.toLowerCase().includes(searchText.toLowerCase())
              : false)
        )
      })
    },
    [fetchData, labelKey, disabledItemsSection?.enabled, disabledItemsKey],
  )

  const getDisabledList = useCallback(
    (totalItems: DataItem[], searchText: string) => {
      return totalItems.filter((item) => {
        const labelValue = item[labelKey]
        return (
          item[disabledItemsKey] &&
          (fetchData
            ? true
            : typeof labelValue === 'string' && labelValue
              ? labelValue.toLowerCase().includes(searchText.toLowerCase())
              : false)
        )
      })
    },
    [fetchData, labelKey, disabledItemsKey],
  )

  const [unselectedList, setUnselectedList] = useState<DataItem[]>(() =>
    getUnselectedList(
      filteredSelectedList.map((item) => item[labelKey]),
      options,
      search,
    ),
  )

  const [disabledItemsList, setDisabledItemsList] = useState<DataItem[]>(() =>
    getDisabledList(options, search),
  )

  useEffect(() => {
    setUnselectedList(
      getUnselectedList(
        filteredSelectedList.map((item) => item[labelKey]),
        options,
        search,
      ),
    )
    setDisabledItemsList(getDisabledList(options, search))
  }, [
    search,
    options,
    getUnselectedList,
    getDisabledList,
    filteredSelectedList,
    labelKey,
  ])

  const selectedTexts = useMemo(
    () => selectedList.map((item) => item[labelKey]),
    [labelKey, selectedList],
  )

  const searchCallout = (searchText: string) => {
    setSearch(searchText)
    onSearchChange?.(searchText)
    // Update unselectedList when search text changes
    setUnselectedList(getUnselectedList(selectedTexts, options, searchText))
  }

  const selectedAndDisabled = useMemo(
    () => selectedList.filter((item) => item.disabled),
    [selectedList],
  )

  const [actionVisible, setActionVisible] = useState(false)
  const openClass = actionVisible ? styles.open : ''

  const showActionContent = useCallback(() => {
    setTimeout(() => setActionVisible(true), 250) //  To track the mounting of PopoverContent after the animation so that the searchBarRef is set correctly
    dropdownState?.('open')
  }, [dropdownState])

  const hideActionContent = useCallback(() => {
    setActionVisible(false)
    dropdownState?.('close')
    if (selectedOptions) {
      callout?.(selectedList)
    }
    setSearch('')
  }, [callout, dropdownState, selectedList, selectedOptions])

  const onClearClick = () => {
    setSelectedList([])
    callout?.(selectedAndDisabled)
  }

  const getClickText = () =>
    selectedTexts?.length ? (
      `(${selectedTexts.length}) ${selectedTexts.map((el) => el).join(', ')}`
    ) : (
      <span className={styles.placeholder}>{selectPlaceholder}</span>
    )

  const rowCallout = (text: string, shouldCheck: boolean) => {
    // Immediately update unselectedList to remove the item
    if (shouldCheck) {
      setUnselectedList((prev) =>
        prev.filter((item) => item[labelKey] !== text),
      )
    }

    let selected = [...selectedList] // Start with the current selectedList
    if (shouldCheck) {
      const itemToAdd = options.find((item) => item[labelKey] === text)
      if (itemToAdd) {
        if (isReorderable) {
          // If we support DraggableList, we just append the selected item to the end of the list
          selected.push(itemToAdd)
        } else {
          // If not reorderable, maintain the order
          const indexToInsert = optionsInitialOrder[text]
          selected.splice(indexToInsert, 0, itemToAdd) // Insert at the correct index
        }
      }
    } else {
      // Immediately update unselectedList to add the item back
      const itemToUnselect = selectedList.find(
        (item) => item[labelKey] === text,
      )
      if (itemToUnselect) {
        setUnselectedList((prev) => [...prev, itemToUnselect])
      }
      selected = selectedList.filter((item) => item[labelKey] !== text)
    }

    if (shouldSortAlphabetically) {
      // Sort the selected list alphabetically
      selected.sort((a, b) => {
        const labelA = a[labelKey] as string
        const labelB = b[labelKey] as string
        return labelA.localeCompare(labelB)
      })
    }

    setSelectedList(selected) // Update the selectedList state
    callout?.(selected) // Call the provided callback with the updated list
  }

  const enabledUnselectedRecords = useMemo(
    () => unselectedList.filter((item) => !item.disabled),
    [unselectedList],
  )
  const isSelectAllChecked = enabledUnselectedRecords.length === 0

  const checkAll = useCallback(() => {
    if (isSelectAllChecked) {
      // When options are fetched from the API in the consumer component
      setSelectedList([])
      callout?.(selectedAndDisabled)
      if (nestedConfig) {
        const unCheckedAllState = nestedConfig.itemStates.map((item) => {
          item.state = CheckboxState.UNCHECKED
          return item
        })

        if (unCheckedAllState) {
          nestedConfig.setItemStates(unCheckedAllState)
        }
      }
    } else {
      // When options are fetched from the API in the consumer component
      const selected = [...selectedList, ...enabledUnselectedRecords]
      setSelectedList(selected)
      callout?.(selected)
      if (nestedConfig) {
        const checkedAllState = nestedConfig.itemStates.map((item) => {
          item.state = CheckboxState.CHECKED
          return item
        })

        if (checkedAllState) {
          nestedConfig.setItemStates(checkedAllState)
        }
      }
    }
  }, [
    callout,
    enabledUnselectedRecords,
    isSelectAllChecked,
    selectedAndDisabled,
    selectedList,
    nestedConfig,
  ])

  // infinite scroll & pagination
  const [hasMoreNode, setHasMoreNode] = useState<HTMLDivElement | null>(null)
  useEffect(() => {
    if (!loading && !isFetching && hasMoreNode && hasMore) {
      const observer = new IntersectionObserver(
        (entries) => {
          entries.forEach((entry) => {
            if (entry.intersectionRatio > 0) {
              fetchData()
            }
          })
        },
        {
          threshold: 0.3,
        },
      )

      observer.observe(hasMoreNode)

      return () => {
        observer.disconnect()
      }
    }
  }, [hasMore, fetchData, isFetching, loading, hasMoreNode])

  const searchBarRef = useFocus(!!actionVisible)
  const MultiSelectContent = () => {
    const height = maxHeight ? maxHeight : 300
    const hasSearch = hasValue(showSearch) ? showSearch : options?.length > 10
    const noBorder = !exposed || !!removeBorder
    const showGradient = hasSearch

    const showHeaders = filteredSelectedList.length > 0

    const getHeader = (type: 'selected' | 'unselected' | 'disabled') => {
      let count: number
      let headerTitle = c(type)
      switch (type) {
        case 'selected':
          count = selectedList.length
          break
        case 'unselected':
          count = unselectedList.length
          break
        case 'disabled':
          count = disabledItemsList.length
          headerTitle = disabledItemsSection?.enabled
            ? (disabledItemsSection?.primaryTitle ?? c('notAvailable'))
            : ''
          break
      }

      return (
        <div
          className={`${styles.stickyHeader} ${
            showGradient ? styles.hasGradient : ''
          }`}
        >
          <SectionHeader
            title={`${headerTitle} ${count ? ` (${count}) ` : ''}`}
            rightDisplay={
              type === 'selected' ? (
                <Button
                  as='button'
                  styleType='text-blue'
                  onClick={onClearClick}
                  disabled={notEmpty(search)}
                  {...(notEmpty(search)
                    ? {
                        tooltip: {
                          tooltipContent: c('tooltipClearDisabled'),
                        },
                      }
                    : {})}
                >
                  {c('clear')}
                </Button>
              ) : type === 'disabled' && disabledItemsSection?.enabled ? (
                <span className={styles.secondaryValueHeaderTitle}>
                  {disabledItemsSection?.secondaryTitle}
                </span>
              ) : null
            }
            {...(type === 'selected' && notEmpty(search)
              ? {
                  tooltip: {
                    tooltipContent: c('tooltipMultiSelectSearch'),
                  },
                }
              : {})}
          />
        </div>
      )
    }

    const findCategoryById = (
      categories: DataItem[],
      targetId: number,
    ): DataItem | null => {
      for (const category of categories) {
        if (category.id === targetId) {
          return category // Found the category
        }
        if ((category.children as DataItem[]).length > 0) {
          const result = findCategoryById(
            category.children as DataItem[],
            targetId,
          )
          if (result) {
            return result // Found in child categories
          }
        }
      }
      return null // Not found
    }

    const findExtremeParentIds = (
      categories: DataItem[],
      ids: number[],
    ): number[] => {
      const allParents = new Set<number>() // Track potential parent IDs
      const allChildren = new Set<number>() // Track child IDs within the list

      const traverse = (categories: DataItem[]): void => {
        for (const category of categories) {
          if (ids.includes(category.id as number)) {
            allParents.add(category.id as number) // Add current category ID as a potential parent
            // Add its children's IDs to the children set if they are in the ids array
            if (category.children && Array.isArray(category.children)) {
              category.children.forEach((child) => {
                if (ids.includes(child.id as number)) {
                  allChildren.add(child.id as number)
                }
              })
            }
          }
          if (
            category.children &&
            Array.isArray(category.children) &&
            category.children.length > 0
          ) {
            traverse(category.children) // Recursively process children
          }
        }
      }

      // Start traversing the hierarchy
      traverse(categories as DataItem[])

      // The extreme parents are those in allParents but not in allChildren
      return Array.from(allParents).filter((id) => !allChildren.has(id))
    }

    const clickHandler = (id: string | number) => {
      const newState = updateItemStates(
        nestedConfig?.ignoreNodeHierarchy ?? false,
        nestedConfig?.itemStates ?? [],
        nestedConfig?.data ?? [],
        id,
        idKey,
        parentKey,
      )

      const filteredCheckedItemIds = newState
        .filter((item) => item.state === CheckboxState.CHECKED)
        .map((item) => parseInt(String(item.id)))

      const extremeParentIds = findExtremeParentIds(
        options,
        filteredCheckedItemIds,
      )

      const selectedOptions = extremeParentIds
        .map((id) => findCategoryById(options, id))
        .filter((option) => option !== null) as DataItem[]

      setSelectedList(selectedOptions)
      nestedConfig?.setItemStates(newState)
      callout?.(selectedOptions)
    }

    const getStateForId = (id: string | number) => {
      return (
        nestedConfig?.itemStates?.find((i) => i.id === id)?.state ||
        CheckboxState.UNCHECKED
      )
    }

    const renderListItem = (listItem: DataItem, isChecked: boolean) => {
      const selectText = listItem[labelKey] as string
      const isSecondaryValuePresent = listItem.secondaryOption
      const RowNode = (
        <Checkbox
          name='checkbox'
          type='checkbox'
          callout={(itemText, shouldCheck) => {
            !listItem.disabled &&
              rowCallout((itemText ?? '')?.toString(), shouldCheck)
          }}
          checked={isChecked}
          customClass={styles.listItem}
          label={selectText}
          stateName={selectText}
          disabled={listItem.disabled}
        />
      )
      return (
        <div className={styles.multiselectionRow}>
          {RowNode}
          <div className={styles.rightAlignedRowItems}>
            {isSecondaryValuePresent ? (
              <Button
                as='unstyled'
                className={`${styles.secondaryValue} ${disabledItemsSection?.enabled ? styles.valueTextInNormalCase : ''}`}
              >
                {listItem.secondaryOption}
              </Button>
            ) : (
              <></>
            )}
            {isReorderable && isChecked && filteredSelectedList.length > 1 ? (
              <DragHandle />
            ) : null}
          </div>
        </div>
      )
    }

    // Calculate popover width: use select box width when no search, otherwise use the larger of select box or min search width (216px = 200px minWidth + 16px padding)
    const popoverWidth = selectBoxRef.current?.offsetWidth
      ? hasSearch
        ? Math.max(selectBoxRef.current.offsetWidth, 216)
        : selectBoxRef.current.offsetWidth
      : undefined

    return (
      <div
        className={`${styles.multiSelect} ${noBorder ? styles.noBorder : ''} ${
          !showGradient ? styles.addGap : ''
        } ${exposed ? styles.exposed : ''} ${hideSelectAll && !hasSearch ? styles.noSelectAll : ''}`}
        style={{
          ...(exposed ? { maxHeight: height } : {}),
          ...(popoverWidth ? { width: popoverWidth } : {}),
        }}
      >
        {/* Search Bar */}
        {hasSearch ? (
          <div className={styles.searchContainerPadding}>
            <SearchBar
              value={search}
              placeholder={searchPlaceholder}
              onChange={searchCallout}
              // This min-width is needed to maintain responsiveness. Otherwise the searchbar goes out of the Selectbox boundaries.
              minWidth={200}
              ref={searchBarRef}
              autoFocus={autoFocusSearch}
            />
          </div>
        ) : null}
        {/* List Items (With selected and unselected Group Headers) */}
        <ScrollingContainer height='auto' hideGradients={!showGradient}>
          <div className={styles.list}>
            {loading ? (
              <SelectLoading initialLoading />
            ) : nestedConfig ? (
              search && filteredItems?.length === 0 ? (
                <EmptyState {...emptyStateProps} />
              ) : (
                <CheckboxList
                  items={
                    search
                      ? nestedConfig.filterItems
                        ? filteredItems.filter(nestedConfig.filterItems)
                        : filteredItems
                      : nestedConfig?.data
                  }
                  onClick={clickHandler}
                  expandedItems={expandedItems}
                  onToggleExpand={onToggleExpand}
                  getStateForId={getStateForId}
                  tableConfig={nestedConfig?.tableConfig}
                  idKey={idKey}
                  parentKey={parentKey}
                  displayKey={displayKey}
                  pathKey='pathId'
                  className={styles.checkboxListRow}
                  groupScrollConfig={nestedConfig?.groupScrollConfig}
                />
              )
            ) : (
              <>
                {/* Selected Group Header */}
                {showHeaders ? getHeader('selected') : null}
                {/* Selected List Items */}
                {filteredSelectedList.length ? (
                  <DraggableList
                    callout={(newList) => {
                      setSelectedList(newList) // Update selectedList with the new order
                      callout?.(newList) // Call the provided callback with the updated list
                    }}
                    listItems={filteredSelectedList}
                    wrapperClass={styles.selectedRowsWrapper}
                  >
                    {filteredSelectedList.map((listItem, index) => {
                      return (
                        <Fragment key={`selected-${index}`}>
                          {renderListItem(listItem, true)}
                        </Fragment>
                      )
                    })}
                  </DraggableList>
                ) : null}
                {/* Unselected Group Header */}
                {showHeaders ? getHeader('unselected') : null}
                {/* Unselected List Items */}
                {options.length === 0 ||
                (search && unselectedList.length === 0) ? (
                  <EmptyState {...emptyStateProps} />
                ) : (
                  unselectedList.map((listItem, index) => (
                    <Fragment key={`unselected-${index}`}>
                      {renderListItem(listItem, false)}
                    </Fragment>
                  ))
                )}
                {disabledItemsList.length > 0 && disabledItemsSection?.enabled
                  ? getHeader('disabled')
                  : null}
                {disabledItemsSection?.enabled &&
                  disabledItemsList.length > 0 &&
                  disabledItemsList.map((listItem, index) => (
                    <Fragment key={`disabled-${index}`}>
                      {renderListItem({ ...listItem, disabled: true }, false)}
                    </Fragment>
                  ))}
                {/* Infinite Scroll Loader */}
                {hasMore ? (
                  <div ref={setHasMoreNode}>
                    <SelectLoading />
                  </div>
                ) : null}
              </>
            )}
          </div>
        </ScrollingContainer>
        {/* Select All */}
        {!hideSelectAll ? (
          <div className={styles.selectAll}>
            <Button as='unstyled' onClick={checkAll}>
              {`${c('selectAll')} (${selectedList?.length + unselectedList?.length})`}
            </Button>
          </div>
        ) : null}
      </div>
    )
  }

  return (
    <div qa-test-id={qaTestId} onFocus={onFocus}>
      {formLabelProps ? (
        <FormLabel {...formLabelProps} disabled={disabled} />
      ) : null}
      {exposed ? (
        MultiSelectContent()
      ) : (
        <Popover
          position='bottom'
          maxWidth='450px'
          appendTo={
            appendTo
              ? (appendTo as PopoverProps['appendTo'])
              : isClient
                ? document.body
                : 'parent'
          }
          popoverContent={MultiSelectContent()}
          noPadding
          onClickOutside={hideActionContent}
        >
          {({ visible, setVisible }) => (
            <div
              onClick={
                !disabled
                  ? actionVisible
                    ? () => {
                        setVisible(false)
                        hideActionContent()
                      }
                    : () => {
                        setVisible(true)
                        showActionContent()
                      }
                  : undefined
              }
              className={`${styles.popoverToggle} ${
                disabled ? styles.disabled : ''
              }`}
            >
              <div
                className={`${styles.selectBox} ${openClass}`}
                ref={selectBoxRef}
              >
                <span className={`${styles.selectedText} ${openClass}`}>
                  {getClickText()}
                </span>
                <span className={visible ? styles.flip : ''}>
                  <Icon icon='carat' iconSize='8px' />
                </span>
              </div>
            </div>
          )}
        </Popover>
      )}
    </div>
  )
}

export default MultiSelect
