import React, { forwardRef, HTMLAttributes, JSX, ReactNode } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

import { CElementCover } from '../element-cover'
import { CVirtualScroller } from '../virtual-scroller'

import { getNextSibling, getPreviousSibling } from '../../utils'

import type { Option, OptionsGroup } from './types'
import { getOptionLabel, highlightSubstring, isOptionDisabled, isOptionSelected } from './utils'

export interface CAutocompleteOptionsProps extends HTMLAttributes<HTMLDivElement> {
  highlightOptionsOnSearch?: boolean
  loading?: boolean
  onOptionClick?: (option: Option) => void
  options: (Option | OptionsGroup)[]
  optionsMaxHeight?: number | string
  optionsTemplate?: (option: Option) => ReactNode
  optionsGroupsTemplate?: (option: OptionsGroup) => ReactNode
  searchNoResultsLabel?: boolean | string | ReactNode
  searchValue?: string
  selected: Option | null
  virtualScroller?: boolean
  visibleItems?: number
}

export const CAutocompleteOptions = forwardRef<HTMLDivElement, CAutocompleteOptionsProps>(
  (
    {
      highlightOptionsOnSearch,
      loading,
      onOptionClick,
      options,
      optionsMaxHeight,
      optionsTemplate,
      optionsGroupsTemplate,
      searchNoResultsLabel,
      searchValue,
      selected,
      virtualScroller,
      visibleItems = 10,
    },
    ref
  ) => {
    const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>, option: Option) => {
      if (event.code === 'Space' || event.key === 'Enter') {
        event.preventDefault()
        onOptionClick?.(option)
      }

      if (event.key === 'Down' || event.key === 'ArrowDown') {
        event.preventDefault()
        const target = event.target as HTMLElement
        const next = getNextSibling(
          target,
          '.autocomplete-option:not(.disabled):not(:disabled)'
        ) as HTMLElement | null

        if (next) {
          next.focus()
        }
      }

      if (event.key === 'Up' || event.key === 'ArrowUp') {
        event.preventDefault()
        const target = event.target as HTMLElement
        const prev = getPreviousSibling(
          target,
          '.autocomplete-option:not(.disabled):not(:disabled)'
        ) as HTMLElement | null

        if (prev) {
          prev.focus()
        }
      }
    }

    const createOption = (option: Option, index: number) => (
      <div
        className={classNames('autocomplete-option', {
          disabled: isOptionDisabled(option),
          selected: isOptionSelected(option, selected),
        })}
        key={index}
        onClick={() => onOptionClick && onOptionClick(option as Option)}
        onKeyDown={(event) => handleKeyDown(event, option as Option)}
        tabIndex={0}
      >
        {optionsTemplate ? (
          optionsTemplate(option as Option)
        ) : highlightOptionsOnSearch ? (
          <div
            dangerouslySetInnerHTML={{
              __html: highlightSubstring(getOptionLabel(option), searchValue),
            }}
          ></div>
        ) : (
          getOptionLabel(option)
        )}
      </div>
    )

    const createOptions = (options: (Option | OptionsGroup)[]): JSX.Element | JSX.Element[] =>
      options.length > 0 ? (
        options.map((option: Option | OptionsGroup | string, index: number) =>
          typeof option !== 'string' && 'options' in option ? (
            <React.Fragment key={index}>
              <div className="autocomplete-optgroup-label">
                {optionsGroupsTemplate
                  ? optionsGroupsTemplate(option as OptionsGroup)
                  : option.label}
              </div>
              {option.options?.map((option: Option, index: number) => createOption(option, index))}
            </React.Fragment>
          ) : (
            createOption(option as Option, index)
          )
        )
      ) : (
        <div className="autocomplete-options-empty">{searchNoResultsLabel}</div>
      )

    return (
      <>
        {virtualScroller ? (
          <CVirtualScroller
            className="autocomplete-options"
            visibleItems={visibleItems}
            role="listbox"
            ref={ref}
          >
            {createOptions(options)}
          </CVirtualScroller>
        ) : (
          <div
            className="autocomplete-options"
            {...(optionsMaxHeight !== 'auto' && {
              style: { maxHeight: optionsMaxHeight, overflow: 'scroll' },
            })}
            role="listbox"
            ref={ref}
          >
            {createOptions(options)}
          </div>
        )}
        {loading && <CElementCover />}
      </>
    )
  }
)

CAutocompleteOptions.propTypes = {
  loading: PropTypes.bool,
  onOptionClick: PropTypes.func,
  options: PropTypes.array.isRequired,
  optionsMaxHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  optionsTemplate: PropTypes.func,
  optionsGroupsTemplate: PropTypes.func,
  searchNoResultsLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  virtualScroller: PropTypes.bool,
  visibleItems: PropTypes.number,
}

CAutocompleteOptions.displayName = 'CAutocompleteOptions'
